diff --git a/meta/recipes-devtools/python/python-pyasn1.inc b/meta/recipes-devtools/python/python-pyasn1.inc index 1780ee1d88..ae96f09fb1 100644 --- a/meta/recipes-devtools/python/python-pyasn1.inc +++ b/meta/recipes-devtools/python/python-pyasn1.inc @@ -20,6 +20,7 @@ SRC_URI += " \ file://run-ptest \ file://CVE-2026-23490.patch \ file://CVE-2026-59886.patch \ + file://CVE-2026-59884.patch \ " RDEPENDS:${PN}-ptest += " \ diff --git a/meta/recipes-devtools/python/python3-pyasn1/CVE-2026-59884.patch b/meta/recipes-devtools/python/python3-pyasn1/CVE-2026-59884.patch new file mode 100644 index 0000000000..dd897e2d75 --- /dev/null +++ b/meta/recipes-devtools/python/python3-pyasn1/CVE-2026-59884.patch @@ -0,0 +1,245 @@ +From 38e8ae286160eb27620e7cb42108b3b28d1f299f Mon Sep 17 00:00:00 2001 +From: Simon Pichugin +Date: Wed, 8 Jul 2026 17:36:30 -0700 +Subject: [PATCH] Merge commit from fork + +CVE: CVE-2026-59884 +Upstream-Status: Backport [https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5] + +(cherry picked from commit 628e36ecbb5277a3f01572ce418ef54271b165a5) +Signed-off-by: Emily Vekariya +--- + pyasn1/codec/ber/decoder.py | 13 +++++++++++-- + pyasn1/type/tag.py | 20 ++++++++++++++++---- + tests/codec/ber/test_decoder.py | 25 +++++++++++++++++++++++++ + tests/codec/cer/test_decoder.py | 15 +++++++++++++++ + tests/codec/der/test_decoder.py | 15 +++++++++++++++ + tests/type/test_tag.py | 20 ++++++++++++++++++++ + 6 files changed, 102 insertions(+), 6 deletions(-) + +diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py +index be8ba65..18865c2 100644 +--- a/pyasn1/codec/ber/decoder.py ++++ b/pyasn1/codec/ber/decoder.py +@@ -39,6 +39,10 @@ SubstrateUnderrunError = error.SubstrateUnderrunError + # 20 octets allows up to 140-bit integers, supporting UUID-based OIDs + MAX_OID_ARC_CONTINUATION_OCTETS = 20 + ++# Maximum number of octets in a long-form tag ID (20 octets = up to ++# 140-bit tag IDs, matching the OID arc limit) ++MAX_TAG_OCTETS = 20 ++ + + class AbstractPayloadDecoder(object): + protoComponent = None +@@ -1570,7 +1574,7 @@ class SingleItemDecoder(object): + + if tagId == 0x1F: + isShortTag = False +- lengthOctetIdx = 0 ++ tagOctetCount = 0 + tagId = 0 + + while True: +@@ -1584,7 +1588,12 @@ class SingleItemDecoder(object): + ) + + integerTag = ord(integerByte) +- lengthOctetIdx += 1 ++ tagOctetCount += 1 ++ if tagOctetCount > MAX_TAG_OCTETS: ++ raise error.PyAsn1Error( ++ 'Tag ID octet count exceeds limit (%d)' % ( ++ MAX_TAG_OCTETS,) ++ ) + tagId <<= 7 + tagId |= (integerTag & 0x7F) + +diff --git a/pyasn1/type/tag.py b/pyasn1/type/tag.py +index a21a405..bbbdd85 100644 +--- a/pyasn1/type/tag.py ++++ b/pyasn1/type/tag.py +@@ -34,6 +34,16 @@ tagCategoryExplicit = 0x02 + tagCategoryUntagged = 0x04 + + ++def _tagIdToStr(tagId): ++ # Decimal rendering of a huge tag ID can exceed the interpreter's ++ # integer-to-string conversion limit (sys.get_int_max_str_digits(), ++ # Python 3.11+) and raise ValueError; hexadecimal is not limited ++ try: ++ return str(tagId) ++ except ValueError: ++ return hex(tagId) ++ ++ + class Tag(object): + """Create ASN.1 tag + +@@ -56,7 +66,8 @@ class Tag(object): + """ + def __init__(self, tagClass, tagFormat, tagId): + if tagId < 0: +- raise error.PyAsn1Error('Negative tag ID (%s) not allowed' % tagId) ++ raise error.PyAsn1Error( ++ 'Negative tag ID (%s) not allowed' % _tagIdToStr(tagId)) + self.__tagClass = tagClass + self.__tagFormat = tagFormat + self.__tagId = tagId +@@ -65,7 +76,7 @@ class Tag(object): + + def __repr__(self): + representation = '[%s:%s:%s]' % ( +- self.__tagClass, self.__tagFormat, self.__tagId) ++ self.__tagClass, self.__tagFormat, _tagIdToStr(self.__tagId)) + return '<%s object, tag %s>' % ( + self.__class__.__name__, representation) + +@@ -194,8 +205,9 @@ class TagSet(object): + self.__hash = hash(self.__superTagsClassId) + + def __repr__(self): +- representation = '-'.join(['%s:%s:%s' % (x.tagClass, x.tagFormat, x.tagId) +- for x in self.__superTags]) ++ representation = '-'.join( ++ ['%s:%s:%s' % (x.tagClass, x.tagFormat, _tagIdToStr(x.tagId)) ++ for x in self.__superTags]) + if representation: + representation = 'tags ' + representation + else: +diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py +index f6ff7b0..0152027 100644 +--- a/tests/codec/ber/test_decoder.py ++++ b/tests/codec/ber/test_decoder.py +@@ -34,6 +34,31 @@ class LargeTagDecoderTestCase(BaseTestCase): + def testLongTag(self): + assert decoder.decode(ints2octs((0x1f, 2, 1, 0)))[0].tagSet == univ.Integer.tagSet + ++ def testVeryLongTagRoundTrip(self): ++ # (1 << 140) - 1 is the largest tag ID fitting the 20 octet limit ++ for tagId in (1 << 77, (1 << 140) - 1): ++ largeTag = tag.Tag(tag.tagClassContext, tag.tagFormatSimple, tagId) ++ asn1Spec = univ.Integer().subtype(implicitTag=largeTag) ++ value = univ.Integer(1).subtype(implicitTag=largeTag) ++ ++ decoded, rest = decoder.decode(encoder.encode(value), asn1Spec=asn1Spec) ++ ++ assert rest == b'' ++ assert decoded == 1 ++ ++ def testExcessiveLongTag(self): ++ # 1 << 140 is the smallest tag ID needing 21 octets, one over the limit ++ excessiveTag = tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1 << 140) ++ asn1Spec = univ.Integer().subtype(implicitTag=excessiveTag) ++ substrate = encoder.encode(univ.Integer(1).subtype(implicitTag=excessiveTag)) ++ ++ try: ++ decoder.decode(substrate, asn1Spec=asn1Spec) ++ except error.PyAsn1Error: ++ pass ++ else: ++ assert 0, 'excessive long tag tolerated' ++ + def testTagsEquivalence(self): + integer = univ.Integer(2).subtype(implicitTag=tag.Tag(tag.tagClassContext, 0, 0)) + assert decoder.decode(ints2octs((0x9f, 0x80, 0x00, 0x02, 0x01, 0x02)), asn1Spec=integer) == decoder.decode( +diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py +index 3d27194..d759f76 100644 +--- a/tests/codec/cer/test_decoder.py ++++ b/tests/codec/cer/test_decoder.py +@@ -67,6 +67,21 @@ class OctetStringDecoderTestCase(BaseTestCase): + # TODO: test failures on short chunked and long unchunked substrate samples + + ++class LargeTagDecoderTestCase(BaseTestCase): ++ def testExcessiveLongTag(self): ++ # 1 << 140 is the smallest tag ID needing 21 octets, one over the limit ++ excessiveTag = tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1 << 140) ++ asn1Spec = univ.Integer().subtype(implicitTag=excessiveTag) ++ substrate = encoder.encode(univ.Integer(1).subtype(implicitTag=excessiveTag)) ++ ++ try: ++ decoder.decode(substrate, asn1Spec=asn1Spec) ++ except PyAsn1Error: ++ pass ++ else: ++ assert 0, 'excessive long tag tolerated' ++ ++ + class RealDecoderTestCase(BaseTestCase): + def testLargeBinaryRoundTrip(self): + substrate = encoder.encode(univ.Real((-1, 2, 76354972))) +diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py +index 553563c..726c999 100644 +--- a/tests/codec/der/test_decoder.py ++++ b/tests/codec/der/test_decoder.py +@@ -73,6 +73,21 @@ class OctetStringDecoderTestCase(BaseTestCase): + assert 0, 'chunked encoding tolerated' + + ++class LargeTagDecoderTestCase(BaseTestCase): ++ def testExcessiveLongTag(self): ++ # 1 << 140 is the smallest tag ID needing 21 octets, one over the limit ++ excessiveTag = tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1 << 140) ++ asn1Spec = univ.Integer().subtype(implicitTag=excessiveTag) ++ substrate = encoder.encode(univ.Integer(1).subtype(implicitTag=excessiveTag)) ++ ++ try: ++ decoder.decode(substrate, asn1Spec=asn1Spec) ++ except PyAsn1Error: ++ pass ++ else: ++ assert 0, 'excessive long tag tolerated' ++ ++ + class RealDecoderTestCase(BaseTestCase): + def testCanonicalLargeBinaryReal(self): + substrate = encoder.encode(univ.Real((1, 2, 1000000))) +diff --git a/tests/type/test_tag.py b/tests/type/test_tag.py +index d0ffa07..ab9b8b1 100644 +--- a/tests/type/test_tag.py ++++ b/tests/type/test_tag.py +@@ -9,6 +9,7 @@ import unittest + + from tests.base import BaseTestCase + ++from pyasn1 import error + from pyasn1.type import tag + + +@@ -23,6 +24,19 @@ class TagReprTestCase(TagTestCaseBase): + def testRepr(self): + assert 'Tag' in repr(self.t1) + ++ def testReprHugeTagId(self): ++ # must not hit the interpreter's int-to-str conversion limit ++ hugeTag = tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1 << 100000) ++ assert 'Tag' in repr(hugeTag) ++ ++ def testNegativeHugeTagId(self): ++ try: ++ tag.Tag(tag.tagClassContext, tag.tagFormatSimple, -(1 << 100000)) ++ except error.PyAsn1Error: ++ pass ++ else: ++ assert 0, 'negative tag ID tolerated' ++ + + class TagCmpTestCase(TagTestCaseBase): + def testCmp(self): +@@ -54,6 +68,12 @@ class TagSetReprTestCase(TagSetTestCaseBase): + def testRepr(self): + assert 'TagSet' in repr(self.ts1) + ++ def testReprHugeTagId(self): ++ # must not hit the interpreter's int-to-str conversion limit ++ hugeTagSet = self.ts1.tagImplicitly( ++ tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1 << 100000)) ++ assert 'TagSet' in repr(hugeTagSet) ++ + + class TagSetCmpTestCase(TagSetTestCaseBase): + def testCmp(self): +-- +2.34.1 +