Bug#1145581: trixie-pu: package pyasn1/0.6.1-1+deb13u3
Emmanuel Arias <[email protected]>
| Newsgroups | gmane.linux.debian.devel.release |
|---|---|
| Message-ID | <178768286993.98918.17796882582121035627.reportbug__20373.5323668056$1787683063$gmane$org@debian> |
Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected], [email protected] Control: affects -1 + src:pyasn1 User: [email protected] Usertags: pu [ Reason ] Fixing CVE-2026-59884, CVE-2026-59885 and, CVE-2026-59886 [ Impact ] Denial of service, cpu and memory exhaustion [ Tests ] Build it in debusine https://debusine.debian.net/debian/developers/work-request/1108490/ [ Risks ] No issues as I can see, patches are simple, upstream added unittests. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable
pyasn1-trixie-debdiff.txt
(text/plain, 32 KB)
diff -Nru pyasn1-0.6.1/debian/changelog pyasn1-0.6.1/debian/changelog --- pyasn1-0.6.1/debian/changelog 2026-03-26 16:21:15.000000000 +0000 +++ pyasn1-0.6.1/debian/changelog 2026-08-24 20:02:31.000000000 +0000 @@ -1,3 +1,21 @@ +pyasn1 (0.6.1-1+deb13u3) trixie; urgency=high + + * Team upload. + * CVE-2026-59886: uncontrolled resource consumption when converting + decoded real values. univ.Real convertedts mantissa, base, + exponens to a Python float using exact big-integer exponentiation, + so a real value only a few bytes long could carry a very large exponent. + * CVE-2026-59884: BER/CER/DER decoder denial of service via unbounded + long-form tag IDs. The BER decoder accumulated tag continuation + octets without an upper bound, so a crafted substrate could force + construction of an arbitrarily large integer with quadratic CPU cost + (Closes: #1142388). + * CVE-2026-59885: fix quadratic complexity in OBJECT IDENTIFIER and + RELATIVE-OID decoding and encoding, which allowed denial of service + via a small payload with many arcs. + + -- Emmanuel Arias <[email protected]> Mon, 24 Aug 2026 17:02:31 -0300 + pyasn1 (0.6.1-1+deb13u2) trixie-security; urgency=high * Non-maintainer upload by the Security Team. diff -Nru pyasn1-0.6.1/debian/patches/CVE-2026-59884.patch pyasn1-0.6.1/debian/patches/CVE-2026-59884.patch --- pyasn1-0.6.1/debian/patches/CVE-2026-59884.patch 1970-01-01 00:00:00.000000000 +0000 +++ pyasn1-0.6.1/debian/patches/CVE-2026-59884.patch 2026-08-24 20:02:31.000000000 +0000 @@ -0,0 +1,240 @@ +From: Simon Pichugin <[email protected]> +Date: Wed, 8 Jul 2026 17:36:30 -0700 +Subject: Merge commit from fork + + +Origin: https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-59884 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-59884 +--- + 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 b8a8bd5..b1176c0 100644 +--- a/pyasn1/codec/ber/decoder.py ++++ b/pyasn1/codec/ber/decoder.py +@@ -36,6 +36,10 @@ 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 ++ ++# 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 + MAX_NESTING_DEPTH = 100 + + +@@ -1632,7 +1636,7 @@ class SingleItemDecoder(object): + + if tagId == 0x1F: + isShortTag = False +- lengthOctetIdx = 0 ++ tagOctetCount = 0 + tagId = 0 + + while True: +@@ -1646,7 +1650,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 ccb8b00..28cd3fd 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 b2f0cd3..3a32fb1 100644 +--- a/tests/codec/ber/test_decoder.py ++++ b/tests/codec/ber/test_decoder.py +@@ -41,6 +41,31 @@ class LargeTagDecoderTestCase(BaseTestCase): + def testLongTag(self): + assert decoder.decode(bytes((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(bytes((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 a35895c..c9cfea7 100644 +--- a/tests/codec/cer/test_decoder.py ++++ b/tests/codec/cer/test_decoder.py +@@ -66,6 +66,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 576025c..32be27b 100644 +--- a/tests/codec/der/test_decoder.py ++++ b/tests/codec/der/test_decoder.py +@@ -72,6 +72,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): diff -Nru pyasn1-0.6.1/debian/patches/CVE-2026-59885.patch pyasn1-0.6.1/debian/patches/CVE-2026-59885.patch --- pyasn1-0.6.1/debian/patches/CVE-2026-59885.patch 1970-01-01 00:00:00.000000000 +0000 +++ pyasn1-0.6.1/debian/patches/CVE-2026-59885.patch 2026-08-24 20:02:31.000000000 +0000 @@ -0,0 +1,289 @@ +From: Simon Pichugin <[email protected]> +Date: Wed, 8 Jul 2026 17:37:40 -0700 +Subject: Merge commit from fork + + +Origin: https://github.com/pyasn1/pyasn1/commit/45bdb19eb7df4b3780fe9c912c63e99bffc39dd9 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-59885 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-59885 +--- + pyasn1/codec/ber/decoder.py | 28 +++++++++++++++------------- + pyasn1/codec/ber/encoder.py | 24 ++++++++++++------------ + tests/codec/ber/test_decoder.py | 36 ++++++++++++++++++++++++++++++++++++ + tests/codec/ber/test_encoder.py | 20 ++++++++++++++++++++ + 4 files changed, 83 insertions(+), 25 deletions(-) + +diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py +index 47da67c..b8a8bd5 100644 +--- a/pyasn1/codec/ber/decoder.py ++++ b/pyasn1/codec/ber/decoder.py +@@ -420,14 +420,14 @@ class ObjectIdentifierPayloadDecoder(AbstractSimplePayloadDecoder): + if not chunk: + raise error.PyAsn1Error('Empty substrate') + +- oid = () ++ oid = [] + index = 0 + substrateLen = len(chunk) + while index < substrateLen: + subId = chunk[index] + index += 1 + if subId < 128: +- oid += (subId,) ++ oid.append(subId) + elif subId > 128: + # Construct subid from a number of octets + nextSubId = subId +@@ -443,11 +443,11 @@ class ObjectIdentifierPayloadDecoder(AbstractSimplePayloadDecoder): + subId = (subId << 7) + (nextSubId & 0x7F) + if index >= substrateLen: + raise error.SubstrateUnderrunError( +- 'Short substrate for sub-OID past %s' % (oid,) ++ 'Short substrate for sub-OID past %s' % (tuple(oid),) + ) + nextSubId = chunk[index] + index += 1 +- oid += ((subId << 7) + nextSubId,) ++ oid.append((subId << 7) + nextSubId) + elif subId == 128: + # ASN.1 spec forbids leading zeros (0x80) in OID + # encoding, tolerating it opens a vulnerability. See +@@ -457,15 +457,17 @@ class ObjectIdentifierPayloadDecoder(AbstractSimplePayloadDecoder): + + # Decode two leading arcs + if 0 <= oid[0] <= 39: +- oid = (0,) + oid ++ oid.insert(0, 0) + elif 40 <= oid[0] <= 79: +- oid = (1, oid[0] - 40) + oid[1:] ++ oid[0] -= 40 ++ oid.insert(0, 1) + elif oid[0] >= 80: +- oid = (2, oid[0] - 80) + oid[1:] ++ oid[0] -= 80 ++ oid.insert(0, 2) + else: + raise error.PyAsn1Error('Malformed first OID octet: %s' % chunk[0]) + +- yield self._createComponent(asn1Spec, tagSet, oid, **options) ++ yield self._createComponent(asn1Spec, tagSet, tuple(oid), **options) + + + class RelativeOIDPayloadDecoder(AbstractSimplePayloadDecoder): +@@ -485,14 +487,14 @@ class RelativeOIDPayloadDecoder(AbstractSimplePayloadDecoder): + if not chunk: + raise error.PyAsn1Error('Empty substrate') + +- reloid = () ++ reloid = [] + index = 0 + substrateLen = len(chunk) + while index < substrateLen: + subId = chunk[index] + index += 1 + if subId < 128: +- reloid += (subId,) ++ reloid.append(subId) + elif subId > 128: + # Construct subid from a number of octets + nextSubId = subId +@@ -508,11 +510,11 @@ class RelativeOIDPayloadDecoder(AbstractSimplePayloadDecoder): + subId = (subId << 7) + (nextSubId & 0x7F) + if index >= substrateLen: + raise error.SubstrateUnderrunError( +- 'Short substrate for sub-OID past %s' % (reloid,) ++ 'Short substrate for sub-OID past %s' % (tuple(reloid),) + ) + nextSubId = chunk[index] + index += 1 +- reloid += ((subId << 7) + nextSubId,) ++ reloid.append((subId << 7) + nextSubId) + elif subId == 128: + # ASN.1 spec forbids leading zeros (0x80) in OID + # encoding, tolerating it opens a vulnerability. See +@@ -520,7 +522,7 @@ class RelativeOIDPayloadDecoder(AbstractSimplePayloadDecoder): + # page 7 + raise error.PyAsn1Error('Invalid octet 0x80 in RELATIVE-OID encoding') + +- yield self._createComponent(asn1Spec, tagSet, reloid, **options) ++ yield self._createComponent(asn1Spec, tagSet, tuple(reloid), **options) + + + class RealPayloadDecoder(AbstractSimplePayloadDecoder): +diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py +index d16fb1f..71bbdba 100644 +--- a/pyasn1/codec/ber/encoder.py ++++ b/pyasn1/codec/ber/encoder.py +@@ -325,30 +325,30 @@ class ObjectIdentifierEncoder(AbstractItemEncoder): + else: + raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,)) + +- octets = () ++ octets = [] + + # Cycle through subIds + for subOid in oid: + if 0 <= subOid <= 127: + # Optimize for the common case +- octets += (subOid,) ++ octets.append(subOid) + + elif subOid > 127: + # Pack large Sub-Object IDs +- res = (subOid & 0x7f,) ++ res = [subOid & 0x7f] + subOid >>= 7 + + while subOid: +- res = (0x80 | (subOid & 0x7f),) + res ++ res.append(0x80 | (subOid & 0x7f)) + subOid >>= 7 + + # Add packed Sub-Object ID to resulted Object ID +- octets += res ++ octets.extend(reversed(res)) + + else: + raise error.PyAsn1Error('Negative OID arc %s at %s' % (subOid, value)) + +- return octets, False, False ++ return tuple(octets), False, False + + + class RelativeOIDEncoder(AbstractItemEncoder): +@@ -358,30 +358,30 @@ class RelativeOIDEncoder(AbstractItemEncoder): + if asn1Spec is not None: + value = asn1Spec.clone(value) + +- octets = () ++ octets = [] + + # Cycle through subIds + for subOid in value.asTuple(): + if 0 <= subOid <= 127: + # Optimize for the common case +- octets += (subOid,) ++ octets.append(subOid) + + elif subOid > 127: + # Pack large Sub-Object IDs +- res = (subOid & 0x7f,) ++ res = [subOid & 0x7f] + subOid >>= 7 + + while subOid: +- res = (0x80 | (subOid & 0x7f),) + res ++ res.append(0x80 | (subOid & 0x7f)) + subOid >>= 7 + + # Add packed Sub-Object ID to resulted RELATIVE-OID +- octets += res ++ octets.extend(reversed(res)) + + else: + raise error.PyAsn1Error('Negative RELATIVE-OID arc %s at %s' % (subOid, value)) + +- return octets, False, False ++ return tuple(octets), False, False + + + class RealEncoder(AbstractItemEncoder): +diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py +index 4159212..b2f0cd3 100644 +--- a/tests/codec/ber/test_decoder.py ++++ b/tests/codec/ber/test_decoder.py +@@ -26,6 +26,14 @@ from pyasn1.codec.ber import eoo + from pyasn1 import error + + ++def encode_length(length): ++ if length < 128: ++ return bytes([length]) ++ ++ lengthBytes = length.to_bytes((length.bit_length() + 7) // 8, 'big') ++ return bytes([0x80 | len(lengthBytes)]) + lengthBytes ++ ++ + class LargeTagDecoderTestCase(BaseTestCase): + def testLargeTag(self): + assert decoder.decode(bytes((127, 141, 245, 182, 253, 47, 3, 2, 1, 1))) == (1, b'') +@@ -450,6 +458,20 @@ class ObjectIdentifierDecoderTestCase(BaseTestCase): + bytes((0x06, 0x13, 0x88, 0x37, 0x83, 0xC6, 0xDF, 0xD4, 0xCC, 0xB3, 0xFF, 0xFF, 0xFE, 0xF0, 0xB8, 0xD6, 0xB8, 0xCB, 0xE2, 0xB6, 0x47)) + ) == ((2, 999, 18446744073709551535184467440737095), b'') + ++ def testManySingleByteArcs(self): ++ encodedArcCount = 4096 ++ substrate = ( ++ bytes([0x06]) + ++ encode_length(encodedArcCount) + ++ bytes([0x01] * encodedArcCount) ++ ) ++ ++ value, rest = decoder.decode(substrate) ++ assert rest == b'' ++ assert len(value) == encodedArcCount + 1 ++ assert tuple(value[:3]) == (0, 1, 1) ++ assert tuple(value[-3:]) == (1, 1, 1) ++ + def testExcessiveContinuationOctets(self): + """Test that OID arcs with excessive continuation octets are rejected.""" + # Create a payload with 25 continuation octets (exceeds 20 limit) +@@ -585,6 +607,20 @@ class RelativeOIDDecoderTestCase(BaseTestCase): + bytes((0x0D, 0x13, 0x88, 0x37, 0x83, 0xC6, 0xDF, 0xD4, 0xCC, 0xB3, 0xFF, 0xFF, 0xFE, 0xF0, 0xB8, 0xD6, 0xB8, 0xCB, 0xE2, 0xB6, 0x47)) + ) == ((1079, 18446744073709551535184467440737095), b'') + ++ def testManySingleByteArcs(self): ++ arcCount = 4096 ++ substrate = ( ++ bytes([0x0d]) + ++ encode_length(arcCount) + ++ bytes([0x01] * arcCount) ++ ) ++ ++ value, rest = decoder.decode(substrate) ++ assert rest == b'' ++ assert len(value) == arcCount ++ assert tuple(value[:3]) == (1, 1, 1) ++ assert tuple(value[-3:]) == (1, 1, 1) ++ + def testExcessiveContinuationOctets(self): + """Test that RELATIVE-OID arcs with excessive continuation octets are rejected.""" + # Create a payload with 25 continuation octets (exceeds 20 limit) +diff --git a/tests/codec/ber/test_encoder.py b/tests/codec/ber/test_encoder.py +index 2bda716..6248423 100644 +--- a/tests/codec/ber/test_encoder.py ++++ b/tests/codec/ber/test_encoder.py +@@ -348,6 +348,16 @@ class ObjectIdentifierEncoderTestCase(BaseTestCase): + ) == bytes((0x06, 0x13, 0x88, 0x37, 0x83, 0xC6, 0xDF, 0xD4, 0xCC, 0xB3, 0xFF, 0xFF, 0xFE, 0xF0, 0xB8, 0xD6, + 0xB8, 0xCB, 0xE2, 0xB6, 0x47)) + ++ def testManySingleByteArcs(self): ++ arcCount = 4096 ++ substrate = encoder.encode( ++ univ.ObjectIdentifier((1, 3) + (1,) * arcCount) ++ ) ++ ++ assert substrate == ( ++ bytes([0x06, 0x82, 0x10, 0x01, 0x2B]) + bytes([0x01] * arcCount) ++ ) ++ + + class ObjectIdentifierWithSchemaEncoderTestCase(BaseTestCase): + def testOne(self): +@@ -379,6 +389,16 @@ class RelativeOIDEncoderTestCase(BaseTestCase): + 0xB3, 0xFF, 0xFF, 0xFE, 0xF0, 0xB8, 0xD6, 0xB8, 0xCB, + 0xE2, 0xB6, 0x47)) + ++ def testManySingleByteArcs(self): ++ arcCount = 4096 ++ substrate = encoder.encode( ++ univ.RelativeOID((1,) * arcCount) ++ ) ++ ++ assert substrate == ( ++ bytes([0x0D, 0x82, 0x10, 0x00]) + bytes([0x01] * arcCount) ++ ) ++ + + class RelativeOIDWithSchemaEncoderTestCase(BaseTestCase): + def testOne(self): diff -Nru pyasn1-0.6.1/debian/patches/CVE-2026-59886.patch pyasn1-0.6.1/debian/patches/CVE-2026-59886.patch --- pyasn1-0.6.1/debian/patches/CVE-2026-59886.patch 1970-01-01 00:00:00.000000000 +0000 +++ pyasn1-0.6.1/debian/patches/CVE-2026-59886.patch 2026-08-24 20:02:31.000000000 +0000 @@ -0,0 +1,247 @@ +From: Simon Pichugin <[email protected]> +Date: Wed, 8 Jul 2026 17:32:09 -0700 +Subject: Merge commit from fork + + +Origin: https://github.com/pyasn1/pyasn1/commit/e60c691cb91addb8fcefa2f537e85ede6fb1e886 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-59886 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2026-59886 +--- + pyasn1/type/univ.py | 21 ++++++++++++---- + tests/codec/ber/test_decoder.py | 53 ++++++++++++++++++++++++++++++++++------- + tests/codec/cer/test_decoder.py | 10 ++++++++ + tests/codec/der/test_decoder.py | 19 +++++++++++++++ + tests/type/test_univ.py | 40 +++++++++++++++++++++++++++++++ + 5 files changed, 129 insertions(+), 14 deletions(-) + +diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py +index 9aff5e6..8b786dd 100644 +--- a/pyasn1/type/univ.py ++++ b/pyasn1/type/univ.py +@@ -1362,7 +1362,7 @@ class Real(base.SimpleAsn1Type): + def __normalizeBase10(value): + m, b, e = value + while m and m % 10 == 0: +- m /= 10 ++ m //= 10 + e += 1 + return m, b, e + +@@ -1490,10 +1490,21 @@ class Real(base.SimpleAsn1Type): + def __float__(self): + if self._value in self._inf: + return self._value +- else: +- return float( +- self._value[0] * pow(self._value[1], self._value[2]) +- ) ++ ++ mantissa, base, exponent = self._value ++ ++ if not mantissa: ++ return 0.0 ++ ++ if base == 2: ++ return math.ldexp(float(mantissa), exponent) ++ ++ # base is 10 (prettyIn() rejects everything else); refuse to ++ # materialize astronomically large integers via pow() ++ if exponent > sys.float_info.max_10_exp: ++ raise OverflowError('Real value too large to convert to float') ++ ++ return float(mantissa * pow(base, exponent)) + + def __abs__(self): + return self.clone(abs(float(self))) +diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py +index 3e0e09a..4159212 100644 +--- a/tests/codec/ber/test_decoder.py ++++ b/tests/codec/ber/test_decoder.py +@@ -21,6 +21,7 @@ from pyasn1.type import univ + from pyasn1.type import char + from pyasn1.codec import streaming + from pyasn1.codec.ber import decoder ++from pyasn1.codec.ber import encoder + from pyasn1.codec.ber import eoo + from pyasn1 import error + +@@ -680,17 +681,51 @@ class RealDecoderTestCase(BaseTestCase): + bytes((9, 4, 161, 255, 1, 3)) + ) == (univ.Real((3, 2, -1020)), b'') + +-# TODO: this requires Real type comparison fix ++ def testBin6(self): # large exponent, base = 16 ++ value, rest = decoder.decode( ++ bytes((9, 5, 162, 0, 255, 255, 1)) ++ ) ++ ++ assert tuple(value) == (1, 2, 262140) ++ assert rest == b'' ++ ++ def testBin7(self): # large exponent in 4-octet form, base = 16 ++ value, rest = decoder.decode( ++ bytes((9, 7, 227, 4, 1, 35, 69, 103, 1)) ++ ) + +-# def testBin6(self): +-# assert decoder.decode( +-# bytes((9, 5, 162, 0, 255, 255, 1)) +-# ) == (univ.Real((1, 2, 262140)), b'') ++ assert tuple(value) == (-1, 2, 76354972) ++ assert rest == b'' ++ ++ def testLargeBinaryRoundTrip(self): ++ substrate = encoder.encode(univ.Real((-1, 2, 76354972))) ++ value, rest = decoder.decode(substrate) + +-# def testBin7(self): +-# assert decoder.decode( +-# bytes((9, 7, 227, 4, 1, 35, 69, 103, 1)) +-# ) == (univ.Real((-1, 2, 76354972)), b'') ++ assert tuple(value) == (-1, 2, 76354972) ++ assert rest == b'' ++ ++ def testLongFormBinaryRealExponentLength(self): ++ value, rest = decoder.decode( ++ bytes((9, 6, 0x83, 3, 0x0f, 0x42, 0x40, 1)) ++ ) ++ ++ assert tuple(value) == (1, 2, 1000000) ++ assert rest == b'' ++ ++ def testLargeBinaryPrettyPrintOverflow(self): ++ value, rest = decoder.decode( ++ b'\t\t\xeb\x060662.666\xd0B\x00\x00\x00\x00\x00\x00\x00' ++ ) ++ ++ assert value.prettyPrint() == '<overflow>' ++ assert rest == b'6\xd0B\x00\x00\x00\x00\x00\x00\x00' ++ ++ try: ++ float(value) ++ except OverflowError: ++ pass ++ else: ++ assert 0, '__float__() tolerated overflow' + + def testPlusInf(self): + assert decoder.decode( +diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py +index 24d1999..a35895c 100644 +--- a/tests/codec/cer/test_decoder.py ++++ b/tests/codec/cer/test_decoder.py +@@ -14,6 +14,7 @@ from pyasn1.type import namedtype + from pyasn1.type import opentype + from pyasn1.type import univ + from pyasn1.codec.cer import decoder ++from pyasn1.codec.cer import encoder + from pyasn1.error import PyAsn1Error + + +@@ -65,6 +66,15 @@ class OctetStringDecoderTestCase(BaseTestCase): + # TODO: test failures on short chunked and long unchunked substrate samples + + ++class RealDecoderTestCase(BaseTestCase): ++ def testLargeBinaryRoundTrip(self): ++ substrate = encoder.encode(univ.Real((-1, 2, 76354972))) ++ value, rest = decoder.decode(substrate) ++ ++ assert tuple(value) == (-1, 2, 76354972) ++ assert rest == b'' ++ ++ + class SequenceDecoderWithUntaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + openType = opentype.OpenType( +diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py +index ab24c07..576025c 100644 +--- a/tests/codec/der/test_decoder.py ++++ b/tests/codec/der/test_decoder.py +@@ -14,6 +14,7 @@ from pyasn1.type import namedtype + from pyasn1.type import opentype + from pyasn1.type import univ + from pyasn1.codec.der import decoder ++from pyasn1.codec.der import encoder + from pyasn1.error import PyAsn1Error + + +@@ -71,6 +72,24 @@ class OctetStringDecoderTestCase(BaseTestCase): + assert 0, 'chunked encoding tolerated' + + ++class RealDecoderTestCase(BaseTestCase): ++ def testCanonicalLargeBinaryReal(self): ++ substrate = encoder.encode(univ.Real((1, 2, 1000000))) ++ assert substrate == bytes((9, 5, 0x82, 0x0f, 0x42, 0x40, 1)) ++ ++ value, rest = decoder.decode(substrate) ++ ++ assert tuple(value) == (1, 2, 1000000) ++ assert rest == b'' ++ ++ def testLargeBinaryRoundTrip(self): ++ substrate = encoder.encode(univ.Real((-1, 2, 76354972))) ++ value, rest = decoder.decode(substrate) ++ ++ assert tuple(value) == (-1, 2, 76354972) ++ assert rest == b'' ++ ++ + class SequenceDecoderWithUntaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + openType = opentype.OpenType( +diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py +index c1e88c0..d905579 100644 +--- a/tests/type/test_univ.py ++++ b/tests/type/test_univ.py +@@ -752,9 +752,49 @@ class RealTestCase(BaseTestCase): + def testFloat(self): + assert float(univ.Real(4.0)) == 4.0, '__float__() fails' + ++ def testFloatBase10Precision(self): ++ assert float(univ.Real((3, 10, 23))) == 3e23, '__float__() lost base-10 behavior' ++ ++ def testFloatOverflow(self): ++ try: ++ float(univ.Real((1, 2, 1000000))) ++ except OverflowError: ++ pass ++ else: ++ assert 0, '__float__() tolerated overflow' ++ ++ assert univ.Real((1, 2, 1000000)).prettyPrint() == '<overflow>' ++ ++ def testFloatUnderflow(self): ++ assert float(univ.Real((1, 2, -1000000))) == 0.0, '__float__() failed underflow' ++ ++ def testFloatZeroMantissa(self): ++ assert float(univ.Real((0, 10, 1000000000))) == 0.0, '__float__() failed zero mantissa' ++ assert float(univ.Real((0, 2, 1000000000))) == 0.0, '__float__() failed zero mantissa' ++ ++ def testFloatBase10Overflow(self): ++ try: ++ float(univ.Real((1, 10, sys.float_info.max_10_exp + 1))) ++ except OverflowError: ++ pass ++ else: ++ assert 0, '__float__() tolerated base-10 overflow' ++ ++ def testFloatBase10NormalizedOverflow(self): ++ try: ++ float(univ.Real((10, 10, sys.float_info.max_10_exp))) ++ except OverflowError: ++ pass ++ else: ++ assert 0, '__float__() tolerated normalized base-10 overflow' ++ + def testPrettyIn(self): + assert univ.Real((3, 10, 0)) == 3, 'prettyIn() fails' + ++ def testPrettyInBigBase10Mantissa(self): ++ assert tuple(univ.Real((10 ** 400, 10, 0))) == (1, 10, 400), \ ++ 'prettyIn() big mantissa normalization fails' ++ + # infinite float values + def testStrInf(self): + assert str(univ.Real('inf')) == 'inf', 'str() fails' diff -Nru pyasn1-0.6.1/debian/patches/series pyasn1-0.6.1/debian/patches/series --- pyasn1-0.6.1/debian/patches/series 2026-03-26 16:18:48.000000000 +0000 +++ pyasn1-0.6.1/debian/patches/series 2026-08-24 20:02:31.000000000 +0000 @@ -1,3 +1,6 @@ 0002-Remove-some-theme-options-to-avoid-needless-badges-i.patch CVE-2026-23490.patch CVE-2026-30922.patch +CVE-2026-59886.patch +CVE-2026-59884.patch +CVE-2026-59885.patch