--- Begin Message ---
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
--- End Message ---