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:
Yogita Urade authored and Paul Barker committed 2026-10-07 16:08:05 +01:00
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