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
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

Reply via email to