mirror of
https://git.yoctoproject.org/poky
synced 2026-10-16 17:27:06 +00:00
python3-pyasn1: fix CVE-2026-30922
This patch applies upstream fix for CVE-2026-30922 as referenced in [2], using the upstream commit identified in [1]. [1] https://github.com/pyasn1/pyasn1/commit/25ad481c19fdb006e20485ef3fc2e5b3eff30ef0 [2] https://nvd.nist.gov/vuln/detail/CVE-2026-30922 (From OE-Core rev: a06649f7300f68d8eb9bc3dd12a3f4148aad26e9) Signed-off-by: Yogita Urade <yurade@cisco.com> Signed-off-by: Yoann Congal <yoann.congal@smile.fr> Signed-off-by: Paul Barker <paul@pbarker.dev>
This commit is contained in:
1 parent
74cbee5aae
commit
ee53daff75
2 files changed
+263
No files matched your search
@@ -21,6 +21,7 @@ SRC_URI += " \
|
||||
file://CVE-2026-23490.patch \
|
||||
file://CVE-2026-59886.patch \
|
||||
file://CVE-2026-59884.patch \
|
||||
file://CVE-2026-30922.patch \
|
||||
"
|
||||
|
||||
RDEPENDS:${PN}-ptest += " \
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
From bcab1fc8f72230ad7ee6d9612a5216cc556e2d0a Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <simon.pichugin@gmail.com>
|
||||
Date: Mon, 16 Mar 2026 17:23:11 -0700
|
||||
Subject: [PATCH] Merge commit from fork
|
||||
|
||||
CVE: CVE-2026-30922
|
||||
Upstream-Status: Backport [https://github.com/pyasn1/pyasn1/commit/25ad481c19fdb006e20485ef3fc2e5b3eff30ef0]
|
||||
|
||||
(cherry picked from commit 25ad481c19fdb006e20485ef3fc2e5b3eff30ef0)
|
||||
Signed-off-by: Yogita Urade <yurade@cisco.com>
|
||||
---
|
||||
pyasn1/codec/ber/decoder.py | 10 +++
|
||||
tests/codec/ber/test_decoder.py | 116 ++++++++++++++++++++++++++++++++
|
||||
tests/codec/cer/test_decoder.py | 24 +++++++
|
||||
tests/codec/der/test_decoder.py | 42 ++++++++++++
|
||||
4 files changed, 192 insertions(+)
|
||||
|
||||
diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py
|
||||
index 18865c2..4d34080 100644
|
||||
--- a/pyasn1/codec/ber/decoder.py
|
||||
+++ b/pyasn1/codec/ber/decoder.py
|
||||
@@ -38,6 +38,7 @@ SubstrateUnderrunError = error.SubstrateUnderrunError
|
||||
# Maximum number of continuation octets (high-bit set) allowed per OID arc.
|
||||
# 20 octets allows up to 140-bit integers, supporting UUID-based OIDs
|
||||
MAX_OID_ARC_CONTINUATION_OCTETS = 20
|
||||
+MAX_NESTING_DEPTH = 100
|
||||
|
||||
# Maximum number of octets in a long-form tag ID (20 octets = up to
|
||||
# 140-bit tag IDs, matching the OID arc limit)
|
||||
@@ -1519,6 +1520,15 @@ class SingleItemDecoder(object):
|
||||
decodeFun=None, substrateFun=None,
|
||||
**options):
|
||||
|
||||
+ _nestingLevel = options.get('_nestingLevel', 0)
|
||||
+
|
||||
+ if _nestingLevel > MAX_NESTING_DEPTH:
|
||||
+ raise error.PyAsn1Error(
|
||||
+ 'ASN.1 structure nesting depth exceeds limit (%d)' % MAX_NESTING_DEPTH
|
||||
+ )
|
||||
+
|
||||
+ options['_nestingLevel'] = _nestingLevel + 1
|
||||
+
|
||||
allowEoo = options.pop('allowEoo', False)
|
||||
|
||||
if LOG:
|
||||
diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py
|
||||
index 0152027..672c9af 100644
|
||||
--- a/tests/codec/ber/test_decoder.py
|
||||
+++ b/tests/codec/ber/test_decoder.py
|
||||
@@ -2048,6 +2048,122 @@ class CompressedFilesTestCase(BaseTestCase):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
+class NestingDepthLimitTestCase(BaseTestCase):
|
||||
+ """Test protection against deeply nested ASN.1 structures (CVE prevention)."""
|
||||
+
|
||||
+ def testIndefLenSequenceNesting(self):
|
||||
+ """Deeply nested indefinite-length SEQUENCEs must raise PyAsn1Error."""
|
||||
+ # Each \x30\x80 opens a new indefinite-length SEQUENCE
|
||||
+ payload = b'\x30\x80' * 200
|
||||
+ try:
|
||||
+ decoder.decode(payload)
|
||||
+ except error.PyAsn1Error:
|
||||
+ pass
|
||||
+ else:
|
||||
+ assert False, 'Deeply nested indef-length SEQUENCEs not rejected'
|
||||
+
|
||||
+ def testIndefLenSetNesting(self):
|
||||
+ """Deeply nested indefinite-length SETs must raise PyAsn1Error."""
|
||||
+ # Each \x31\x80 opens a new indefinite-length SET
|
||||
+ payload = b'\x31\x80' * 200
|
||||
+ try:
|
||||
+ decoder.decode(payload)
|
||||
+ except error.PyAsn1Error:
|
||||
+ pass
|
||||
+ else:
|
||||
+ assert False, 'Deeply nested indef-length SETs not rejected'
|
||||
+
|
||||
+ def testDefiniteLenNesting(self):
|
||||
+ """Deeply nested definite-length SEQUENCEs must raise PyAsn1Error."""
|
||||
+ inner = b'\x05\x00' # NULL
|
||||
+ for _ in range(200):
|
||||
+ length = len(inner)
|
||||
+ if length < 128:
|
||||
+ inner = b'\x30' + bytes([length]) + inner
|
||||
+ else:
|
||||
+ length_bytes = length.to_bytes(
|
||||
+ (length.bit_length() + 7) // 8, 'big')
|
||||
+ inner = b'\x30' + bytes([0x80 | len(length_bytes)]) + \
|
||||
+ length_bytes + inner
|
||||
+ try:
|
||||
+ decoder.decode(inner)
|
||||
+ except error.PyAsn1Error:
|
||||
+ pass
|
||||
+ else:
|
||||
+ assert False, 'Deeply nested definite-length SEQUENCEs not rejected'
|
||||
+
|
||||
+ def testNestingUnderLimitWorks(self):
|
||||
+ """Nesting within the limit must decode successfully."""
|
||||
+ inner = b'\x05\x00' # NULL
|
||||
+ for _ in range(50):
|
||||
+ length = len(inner)
|
||||
+ if length < 128:
|
||||
+ inner = b'\x30' + bytes([length]) + inner
|
||||
+ else:
|
||||
+ length_bytes = length.to_bytes(
|
||||
+ (length.bit_length() + 7) // 8, 'big')
|
||||
+ inner = b'\x30' + bytes([0x80 | len(length_bytes)]) + \
|
||||
+ length_bytes + inner
|
||||
+ asn1Object, _ = decoder.decode(inner)
|
||||
+ assert asn1Object is not None, 'Valid nested structure rejected'
|
||||
+
|
||||
+ def testSiblingsDontIncreaseDepth(self):
|
||||
+ """Sibling elements at the same level must not inflate depth count."""
|
||||
+ # SEQUENCE containing 200 INTEGER siblings - should decode fine
|
||||
+ components = b'\x02\x01\x01' * 200 # 200 x INTEGER(1)
|
||||
+ length = len(components)
|
||||
+ length_bytes = length.to_bytes(
|
||||
+ (length.bit_length() + 7) // 8, 'big')
|
||||
+ payload = b'\x30' + bytes([0x80 | len(length_bytes)]) + \
|
||||
+ length_bytes + components
|
||||
+ asn1Object, _ = decoder.decode(payload)
|
||||
+ assert asn1Object is not None, 'Siblings incorrectly rejected'
|
||||
+
|
||||
+ def testErrorMessageContainsLimit(self):
|
||||
+ """Error message must indicate the nesting depth limit."""
|
||||
+ payload = b'\x30\x80' * 200
|
||||
+ try:
|
||||
+ decoder.decode(payload)
|
||||
+ except error.PyAsn1Error as exc:
|
||||
+ assert 'nesting depth' in str(exc).lower(), \
|
||||
+ 'Error message missing depth info: %s' % exc
|
||||
+ else:
|
||||
+ assert False, 'Expected PyAsn1Error'
|
||||
+
|
||||
+ def testNoRecursionError(self):
|
||||
+ """Must raise PyAsn1Error, not RecursionError."""
|
||||
+ payload = b'\x30\x80' * 50000
|
||||
+ try:
|
||||
+ decoder.decode(payload)
|
||||
+ except error.PyAsn1Error:
|
||||
+ pass
|
||||
+ except RecursionError:
|
||||
+ assert False, 'Got RecursionError instead of PyAsn1Error'
|
||||
+
|
||||
+ def testMixedNesting(self):
|
||||
+ """Mixed SEQUENCE and SET nesting must be caught."""
|
||||
+ # Alternate SEQUENCE (0x30) and SET (0x31) with indef length
|
||||
+ payload = b''
|
||||
+ for i in range(200):
|
||||
+ payload += b'\x30\x80' if i % 2 == 0 else b'\x31\x80'
|
||||
+ try:
|
||||
+ decoder.decode(payload)
|
||||
+ except error.PyAsn1Error:
|
||||
+ pass
|
||||
+ else:
|
||||
+ assert False, 'Mixed nesting not rejected'
|
||||
+
|
||||
+ def testWithSchema(self):
|
||||
+ """Deeply nested structures must be caught even with schema."""
|
||||
+ payload = b'\x30\x80' * 200
|
||||
+ try:
|
||||
+ decoder.decode(payload, asn1Spec=univ.Sequence())
|
||||
+ except error.PyAsn1Error:
|
||||
+ pass
|
||||
+ else:
|
||||
+ assert False, 'Deeply nested with schema not rejected'
|
||||
+
|
||||
+
|
||||
class NonStreamingCompatibilityTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
from pyasn1 import debug
|
||||
diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py
|
||||
index d759f76..2994846 100644
|
||||
--- a/tests/codec/cer/test_decoder.py
|
||||
+++ b/tests/codec/cer/test_decoder.py
|
||||
@@ -389,6 +389,30 @@ class SequenceDecoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase):
|
||||
assert s[1][0] == univ.OctetString(hexValue='02010C')
|
||||
|
||||
|
||||
+class NestingDepthLimitTestCase(BaseTestCase):
|
||||
+ """Test CER decoder protection against deeply nested structures."""
|
||||
+
|
||||
+ def testIndefLenNesting(self):
|
||||
+ """Deeply nested indefinite-length SEQUENCEs must raise PyAsn1Error."""
|
||||
+ payload = b'\x30\x80' * 200
|
||||
+ try:
|
||||
+ decoder.decode(payload)
|
||||
+ except PyAsn1Error:
|
||||
+ pass
|
||||
+ else:
|
||||
+ assert False, 'Deeply nested indef-length SEQUENCEs not rejected'
|
||||
+
|
||||
+ def testNoRecursionError(self):
|
||||
+ """Must raise PyAsn1Error, not RecursionError."""
|
||||
+ payload = b'\x30\x80' * 50000
|
||||
+ try:
|
||||
+ decoder.decode(payload)
|
||||
+ except PyAsn1Error:
|
||||
+ pass
|
||||
+ except RecursionError:
|
||||
+ assert False, 'Got RecursionError instead of PyAsn1Error'
|
||||
+
|
||||
+
|
||||
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
|
||||
|
||||
if __name__ == '__main__':
|
||||
diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py
|
||||
index 726c999..3a81a6e 100644
|
||||
--- a/tests/codec/der/test_decoder.py
|
||||
+++ b/tests/codec/der/test_decoder.py
|
||||
@@ -396,6 +396,48 @@ class SequenceDecoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase):
|
||||
assert s[1][0] == univ.OctetString(hexValue='02010C')
|
||||
|
||||
|
||||
+class NestingDepthLimitTestCase(BaseTestCase):
|
||||
+ """Test DER decoder protection against deeply nested structures."""
|
||||
+
|
||||
+ def testDefiniteLenNesting(self):
|
||||
+ """Deeply nested definite-length SEQUENCEs must raise PyAsn1Error."""
|
||||
+ inner = b'\x05\x00' # NULL
|
||||
+ for _ in range(200):
|
||||
+ length = len(inner)
|
||||
+ if length < 128:
|
||||
+ inner = b'\x30' + bytes([length]) + inner
|
||||
+ else:
|
||||
+ length_bytes = length.to_bytes(
|
||||
+ (length.bit_length() + 7) // 8, 'big')
|
||||
+ inner = b'\x30' + bytes([0x80 | len(length_bytes)]) + \
|
||||
+ length_bytes + inner
|
||||
+ try:
|
||||
+ decoder.decode(inner)
|
||||
+ except PyAsn1Error:
|
||||
+ pass
|
||||
+ else:
|
||||
+ assert False, 'Deeply nested definite-length SEQUENCEs not rejected'
|
||||
+
|
||||
+ def testNoRecursionError(self):
|
||||
+ """Must raise PyAsn1Error, not RecursionError."""
|
||||
+ inner = b'\x05\x00'
|
||||
+ for _ in range(200):
|
||||
+ length = len(inner)
|
||||
+ if length < 128:
|
||||
+ inner = b'\x30' + bytes([length]) + inner
|
||||
+ else:
|
||||
+ length_bytes = length.to_bytes(
|
||||
+ (length.bit_length() + 7) // 8, 'big')
|
||||
+ inner = b'\x30' + bytes([0x80 | len(length_bytes)]) + \
|
||||
+ length_bytes + inner
|
||||
+ try:
|
||||
+ decoder.decode(inner)
|
||||
+ except PyAsn1Error:
|
||||
+ pass
|
||||
+ except RecursionError:
|
||||
+ assert False, 'Got RecursionError instead of PyAsn1Error'
|
||||
+
|
||||
+
|
||||
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
|
||||
|
||||
if __name__ == '__main__':
|
||||
--
|
||||
2.44.4
|
||||
Reference in new issue
Block a user