[packages/python-pyasn1] - backported upstream security fixes
arekm
arekm at pld-linux.org
Wed Sep 2 00:14:49 CEST 2026
commit 99caa779053f98be005a90fe9ebc71ec522583da
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date: Wed Sep 2 00:12:02 2026 +0200
- backported upstream security fixes
Fixes CVE-2026-23490, CVE-2026-30922, CVE-2026-59884, CVE-2026-59885 and
CVE-2026-59886.
python-pyasn1-CVE-2026-23490.patch | 87 +++++++++++++
python-pyasn1-CVE-2026-30922.patch | 217 +++++++++++++++++++++++++++++++++
python-pyasn1-CVE-2026-59884.patch | 242 +++++++++++++++++++++++++++++++++++++
python-pyasn1-CVE-2026-59885.patch | 141 +++++++++++++++++++++
python-pyasn1-CVE-2026-59886.patch | 122 +++++++++++++++++++
python-pyasn1.spec | 14 ++-
6 files changed, 821 insertions(+), 2 deletions(-)
---
diff --git a/python-pyasn1.spec b/python-pyasn1.spec
index f6b060a..46adb7e 100644
--- a/python-pyasn1.spec
+++ b/python-pyasn1.spec
@@ -12,13 +12,18 @@ Summary(pl.UTF-8): Narzędzia ASN.1 dla Pythona
Name: python-%{module}
# keep 0.5.x here for python2 support
Version: 0.5.1
-Release: 2
+Release: 3
License: BSD-like
Group: Libraries/Python
#Source0Download: https://pypi.org/simple/pyasn1/
Source0: https://files.pythonhosted.org/packages/source/p/pyasn1/%{module}-%{version}.tar.gz
# Source0-md5: 1e8ca05cc7040aaf06e321886715eb7e
-URL: https://github.com/etingof/pyasn1
+Patch0: %{name}-CVE-2026-23490.patch
+Patch1: %{name}-CVE-2026-30922.patch
+Patch2: %{name}-CVE-2026-59884.patch
+Patch3: %{name}-CVE-2026-59885.patch
+Patch4: %{name}-CVE-2026-59886.patch
+URL: https://github.com/pyasn1/pyasn1
BuildRequires: rpmbuild(macros) >= 1.714
%if %{with python2}
BuildRequires: python >= 1:2.7
@@ -79,6 +84,11 @@ Dokumentacja do modułu Pythona ASN.1.
%prep
%setup -q -n %{module}-%{version}
+%patch -P0 -p1
+%patch -P1 -p1
+%patch -P2 -p1
+%patch -P3 -p1
+%patch -P4 -p1
%build
%if %{with python2}
diff --git a/python-pyasn1-CVE-2026-23490.patch b/python-pyasn1-CVE-2026-23490.patch
new file mode 100644
index 0000000..07a9ea5
--- /dev/null
+++ b/python-pyasn1-CVE-2026-23490.patch
@@ -0,0 +1,87 @@
+Backport of the OID arc continuation-octet limit, fixing CVE-2026-23490.
+0.5.x has no RELATIVE-OID type, so only the OBJECT IDENTIFIER decoder and its
+tests are carried over.
+https://github.com/pyasn1/pyasn1/commit/3908f144229eed4df24bd569d16e5991ace44970
+
+--- a/pyasn1/codec/ber/decoder.py
++++ b/pyasn1/codec/ber/decoder.py
+@@ -35,6 +35,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
++
+
+ class AbstractPayloadDecoder(object):
+ protoComponent = None
+@@ -431,7 +435,14 @@
+ # Construct subid from a number of octets
+ nextSubId = subId
+ subId = 0
++ continuationOctetCount = 0
+ while nextSubId >= 128:
++ continuationOctetCount += 1
++ if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS:
++ raise error.PyAsn1Error(
++ 'OID arc exceeds maximum continuation octets limit (%d) '
++ 'at position %d' % (MAX_OID_ARC_CONTINUATION_OCTETS, index)
++ )
+ subId = (subId << 7) + (nextSubId & 0x7F)
+ if index >= substrateLen:
+ raise error.SubstrateUnderrunError(
+--- a/tests/codec/ber/test_decoder.py
++++ b/tests/codec/ber/test_decoder.py
+@@ -450,6 +450,51 @@
+ ints2octs((0x06, 0x13, 0x88, 0x37, 0x83, 0xC6, 0xDF, 0xD4, 0xCC, 0xB3, 0xFF, 0xFF, 0xFE, 0xF0, 0xB8, 0xD6, 0xB8, 0xCB, 0xE2, 0xB6, 0x47))
+ ) == ((2, 999, 18446744073709551535184467440737095), null)
+
++ def testExcessiveContinuationOctets(self):
++ try:
++ decoder.decode(ints2octs((0x06, 26) + (0x81,) * 25 + (0x01,)))
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert 0, 'Excessive continuation octets tolerated'
++
++ def testMaxAllowedContinuationOctets(self):
++ try:
++ decoder.decode(ints2octs((0x06, 21) + (0x81,) * 20 + (0x01,)))
++ except error.PyAsn1Error:
++ assert 0, 'Valid OID with 20 continuation octets rejected'
++
++ def testOneOverContinuationLimit(self):
++ try:
++ decoder.decode(ints2octs((0x06, 22) + (0x81,) * 21 + (0x01,)))
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert 0, '21 continuation octets tolerated'
++
++ def testExcessiveContinuationInSecondArc(self):
++ try:
++ decoder.decode(ints2octs((0x06, 27, 0x55) + (0x81,) * 25 + (0x01,)))
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert 0, 'Excessive continuation in second arc tolerated'
++
++ def testMultipleArcsAtLimit(self):
++ try:
++ decoder.decode(ints2octs(
++ (0x06, 42) + (0x81,) * 20 + (0x01,) + (0x81,) * 20 + (0x01,)))
++ except error.PyAsn1Error:
++ assert 0, 'Multiple valid arcs at limit rejected'
++
++ def testExcessiveContinuationWithMaxBytes(self):
++ try:
++ decoder.decode(ints2octs((0x06, 26) + (0xFF,) * 25 + (0x01,)))
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert 0, 'Excessive 0xFF continuation octets tolerated'
++
+
+ class RealDecoderTestCase(BaseTestCase):
+ def testChar(self):
diff --git a/python-pyasn1-CVE-2026-30922.patch b/python-pyasn1-CVE-2026-30922.patch
new file mode 100644
index 0000000..c6ae1a0
--- /dev/null
+++ b/python-pyasn1-CVE-2026-30922.patch
@@ -0,0 +1,217 @@
+Backport of the decoder nesting depth limit, fixing CVE-2026-30922.
+Upstream's added tests use int.to_bytes() and RecursionError, neither of which
+exists on Python 2; rewritten here since 0.5.x is the Python 2 branch.
+https://github.com/pyasn1/pyasn1/commit/5a49bd1fe93b5b866a1210f6bf0a3924f21572c8
+
+--- a/pyasn1/codec/ber/decoder.py
++++ b/pyasn1/codec/ber/decoder.py
+@@ -38,6 +38,7 @@
+ # Maximum number of continuation octets (high-bit set) allowed per OID arc.
+ # 20 octets allows up to 140-bit integers, supporting UUID-based OIDs
+ MAX_OID_ARC_CONTINUATION_OCTETS = 20
++MAX_NESTING_DEPTH = 100
+
+
+ class AbstractPayloadDecoder(object):
+@@ -1515,6 +1516,15 @@
+ decodeFun=None, substrateFun=None,
+ **options):
+
++ _nestingLevel = options.get('_nestingLevel', 0)
++
++ if _nestingLevel > MAX_NESTING_DEPTH:
++ raise error.PyAsn1Error(
++ 'ASN.1 structure nesting depth exceeds limit (%d)' % MAX_NESTING_DEPTH
++ )
++
++ options['_nestingLevel'] = _nestingLevel + 1
++
+ allowEoo = options.pop('allowEoo', False)
+
+ if LOG:
+--- a/tests/codec/ber/test_decoder.py
++++ b/tests/codec/ber/test_decoder.py
+@@ -1968,6 +1968,101 @@
+ os.remove(path)
+
+
++class NestingDepthLimitTestCase(BaseTestCase):
++ """Protection against deeply nested ASN.1 structures."""
++
++ @staticmethod
++ def _encodeLength(length):
++ if length < 128:
++ return ints2octs((length,))
++ octets = []
++ while length:
++ octets.insert(0, length & 0xFF)
++ length >>= 8
++ return ints2octs([0x80 | len(octets)] + octets)
++
++ def _nest(self, depth):
++ inner = ints2octs((0x05, 0x00))
++ for _ in range(depth):
++ inner = ints2octs((0x30,)) + self._encodeLength(len(inner)) + inner
++ return inner
++
++ def testIndefLenSequenceNesting(self):
++ try:
++ decoder.decode(ints2octs((0x30, 0x80)) * 200)
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert False, 'Deeply nested indef-length SEQUENCEs not rejected'
++
++ def testIndefLenSetNesting(self):
++ try:
++ decoder.decode(ints2octs((0x31, 0x80)) * 200)
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert False, 'Deeply nested indef-length SETs not rejected'
++
++ def testDefiniteLenNesting(self):
++ try:
++ decoder.decode(self._nest(200))
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert False, 'Deeply nested definite-length SEQUENCEs not rejected'
++
++ def testNestingUnderLimitWorks(self):
++ asn1Object, _ = decoder.decode(self._nest(50))
++ assert asn1Object is not None, 'Valid nested structure rejected'
++
++ def testSiblingsDontIncreaseDepth(self):
++ components = ints2octs((0x02, 0x01, 0x01)) * 200
++ payload = ints2octs((0x30,)) + self._encodeLength(len(components)) + components
++ asn1Object, _ = decoder.decode(payload)
++ assert asn1Object is not None, 'Siblings incorrectly rejected'
++
++ def testErrorMessageContainsLimit(self):
++ # PyAsn1Error.__init__ never chains to Exception.__init__, so on
++ # Python 2 the args never reach the exception and str() is empty.
++ try:
++ decoder.decode(ints2octs((0x30, 0x80)) * 200)
++ except error.PyAsn1Error as exc:
++ if sys.version_info[0] >= 3:
++ assert 'nesting depth' in str(exc).lower(), \
++ 'Error message missing depth info: %s' % exc
++ else:
++ assert False, 'Expected PyAsn1Error'
++
++ def testNoRecursionError(self):
++ # RecursionError is a RuntimeError subclass on Python 3 and does not
++ # exist on Python 2, where runaway recursion raises RuntimeError.
++ try:
++ decoder.decode(ints2octs((0x30, 0x80)) * 50000)
++ except error.PyAsn1Error:
++ pass
++ except RuntimeError:
++ assert False, 'Got recursion error instead of PyAsn1Error'
++
++ def testMixedNesting(self):
++ payload = null
++ for i in range(200):
++ payload += ints2octs((0x30, 0x80) if i % 2 == 0 else (0x31, 0x80))
++ try:
++ decoder.decode(payload)
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert False, 'Mixed nesting not rejected'
++
++ def testWithSchema(self):
++ try:
++ decoder.decode(ints2octs((0x30, 0x80)) * 200, asn1Spec=univ.Sequence())
++ except error.PyAsn1Error:
++ pass
++ else:
++ assert False, 'Deeply nested with schema not rejected'
++
++
+ class NonStreamingCompatibilityTestCase(BaseTestCase):
+ def setUp(self):
+ from pyasn1 import debug
+--- a/tests/codec/cer/test_decoder.py
++++ b/tests/codec/cer/test_decoder.py
+@@ -364,6 +364,32 @@
+ assert s[1][0] == univ.OctetString(hexValue='02010C')
+
+
++class NestingDepthLimitTestCase(BaseTestCase):
++ """Test CER decoder protection against deeply nested structures."""
++
++ def testIndefLenNesting(self):
++ """Deeply nested indefinite-length SEQUENCEs must raise PyAsn1Error."""
++ payload = b'\x30\x80' * 200
++ try:
++ decoder.decode(payload)
++ except PyAsn1Error:
++ pass
++ else:
++ assert False, 'Deeply nested indef-length SEQUENCEs not rejected'
++
++ def testNoRecursionError(self):
++ """Must raise PyAsn1Error, not RecursionError."""
++ payload = b'\x30\x80' * 50000
++ try:
++ decoder.decode(payload)
++ except PyAsn1Error:
++ pass
++ except RuntimeError:
++ # RecursionError is a RuntimeError subclass on Python 3 and does
++ # not exist on Python 2.
++ assert False, 'Got recursion error instead of PyAsn1Error'
++
++
+ suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+
+ if __name__ == '__main__':
+--- a/tests/codec/der/test_decoder.py
++++ b/tests/codec/der/test_decoder.py
+@@ -362,6 +362,44 @@
+ assert s[1][0] == univ.OctetString(hexValue='02010C')
+
+
++class NestingDepthLimitTestCase(BaseTestCase):
++ """Test DER decoder protection against deeply nested structures."""
++
++ @staticmethod
++ def _nest(depth):
++ inner = bytes(bytearray((0x05, 0x00)))
++ for _ in range(depth):
++ length = len(inner)
++ if length < 128:
++ header = bytearray((0x30, length))
++ else:
++ octets = []
++ while length:
++ octets.insert(0, length & 0xFF)
++ length >>= 8
++ header = bytearray([0x30, 0x80 | len(octets)] + octets)
++ inner = bytes(header) + inner
++ return inner
++
++ def testDefiniteLenNesting(self):
++ try:
++ decoder.decode(self._nest(200))
++ except PyAsn1Error:
++ pass
++ else:
++ assert False, 'Deeply nested definite-length SEQUENCEs not rejected'
++
++ def testNoRecursionError(self):
++ # RecursionError is a RuntimeError subclass on Python 3 and does not
++ # exist on Python 2, where runaway recursion raises RuntimeError.
++ try:
++ decoder.decode(self._nest(200))
++ except PyAsn1Error:
++ pass
++ except RuntimeError:
++ assert False, 'Got recursion error instead of PyAsn1Error'
++
++
+ suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+
+ if __name__ == '__main__':
diff --git a/python-pyasn1-CVE-2026-59884.patch b/python-pyasn1-CVE-2026-59884.patch
new file mode 100644
index 0000000..f59ecf3
--- /dev/null
+++ b/python-pyasn1-CVE-2026-59884.patch
@@ -0,0 +1,242 @@
+Backport of the long-form tag ID limit, fixing CVE-2026-59884.
+The unhandled ValueError only surfaces on Python 3.11+, where the huge tag ID
+hits the integer-to-string conversion limit; on Python 2 the same input is
+merely quadratic. Both are fixed by bounding the tag to 20 octets.
+https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5
+
+--- a/pyasn1/codec/ber/decoder.py
++++ b/pyasn1/codec/ber/decoder.py
+@@ -38,6 +38,11 @@
+ # 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
+
+
+@@ -1580,7 +1585,7 @@
+
+ if tagId == 0x1F:
+ isShortTag = False
+- lengthOctetIdx = 0
++ tagOctetCount = 0
+ tagId = 0
+
+ while True:
+@@ -1594,7 +1599,12 @@
+ )
+
+ 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)
+
+--- a/pyasn1/type/tag.py
++++ b/pyasn1/type/tag.py
+@@ -34,6 +34,16 @@
+ 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 @@
+ """
+ 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 @@
+
+ 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 @@
+ 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:
+--- a/tests/codec/ber/test_decoder.py
++++ b/tests/codec/ber/test_decoder.py
+@@ -21,6 +21,7 @@
+ 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.compat.octets import ints2octs, str2octs, null
+ from pyasn1 import error
+@@ -33,6 +34,31 @@
+ 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 == null
++ 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(
+--- a/tests/codec/cer/test_decoder.py
++++ b/tests/codec/cer/test_decoder.py
+@@ -14,6 +14,7 @@
+ from pyasn1.type import opentype
+ from pyasn1.type import univ
+ from pyasn1.codec.cer import decoder
++from pyasn1.codec.cer import encoder
+ from pyasn1.compat.octets import ints2octs, str2octs, null
+ from pyasn1.error import PyAsn1Error
+
+@@ -390,6 +391,21 @@
+ assert False, 'Got recursion error instead of PyAsn1Error'
+
+
++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'
++
++
+ suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+
+ if __name__ == '__main__':
+--- a/tests/codec/der/test_decoder.py
++++ b/tests/codec/der/test_decoder.py
+@@ -14,6 +14,7 @@
+ from pyasn1.type import opentype
+ from pyasn1.type import univ
+ from pyasn1.codec.der import decoder
++from pyasn1.codec.der import encoder
+ from pyasn1.compat.octets import ints2octs, null
+ from pyasn1.error import PyAsn1Error
+
+@@ -400,6 +401,21 @@
+ assert False, 'Got recursion error instead of PyAsn1Error'
+
+
++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'
++
++
+ suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+
+ if __name__ == '__main__':
+--- a/tests/type/test_tag.py
++++ b/tests/type/test_tag.py
+@@ -9,6 +9,7 @@
+
+ from tests.base import BaseTestCase
+
++from pyasn1 import error
+ from pyasn1.type import tag
+
+
+@@ -23,6 +24,19 @@
+ 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 @@
+ 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 --git a/python-pyasn1-CVE-2026-59885.patch b/python-pyasn1-CVE-2026-59885.patch
new file mode 100644
index 0000000..881fa3c
--- /dev/null
+++ b/python-pyasn1-CVE-2026-59885.patch
@@ -0,0 +1,141 @@
+Backport of linear-time OID arc accumulation, fixing CVE-2026-59885.
+Appending to a tuple copies it every time, making both decode and encode
+quadratic in the arc count; a list is built instead and frozen once at the end.
+0.5.x has no RELATIVE-OID type, so only the OBJECT IDENTIFIER paths are carried
+over. The added tests pin the refactor's output, not its timing.
+https://github.com/pyasn1/pyasn1/commit/45bdb19eb7df4b3780fe9c912c63e99bffc39dd9
+
+--- a/pyasn1/codec/ber/decoder.py
++++ b/pyasn1/codec/ber/decoder.py
+@@ -429,14 +429,14 @@
+
+ chunk = octs2ints(chunk)
+
+- 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
+@@ -452,11 +452,11 @@
+ 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
+@@ -466,15 +466,17 @@
+
+ # 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 RealPayloadDecoder(AbstractSimplePayloadDecoder):
+--- a/pyasn1/codec/ber/encoder.py
++++ b/pyasn1/codec/ber/encoder.py
+@@ -327,30 +327,30 @@
+ 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 RealEncoder(AbstractItemEncoder):
+--- a/tests/codec/ber/test_decoder.py
++++ b/tests/codec/ber/test_decoder.py
+@@ -476,6 +476,18 @@
+ ints2octs((0x06, 0x13, 0x88, 0x37, 0x83, 0xC6, 0xDF, 0xD4, 0xCC, 0xB3, 0xFF, 0xFF, 0xFE, 0xF0, 0xB8, 0xD6, 0xB8, 0xCB, 0xE2, 0xB6, 0x47))
+ ) == ((2, 999, 18446744073709551535184467440737095), null)
+
++ def testManySingleByteArcs(self):
++ # 4096 arcs, length encoded long-form as 0x82 0x10 0x00
++ encodedArcCount = 4096
++ substrate = ints2octs([0x06, 0x82, 0x10, 0x00] + [0x01] * encodedArcCount)
++
++ value, rest = decoder.decode(substrate)
++
++ assert rest == null
++ assert len(value) == encodedArcCount + 1
++ assert tuple(value[:3]) == (0, 1, 1)
++ assert tuple(value[-3:]) == (1, 1, 1)
++
+ def testExcessiveContinuationOctets(self):
+ try:
+ decoder.decode(ints2octs((0x06, 26) + (0x81,) * 25 + (0x01,)))
+--- a/tests/codec/ber/test_encoder.py
++++ b/tests/codec/ber/test_encoder.py
+@@ -349,6 +349,16 @@
+ ) == ints2octs((0x06, 0x13, 0x88, 0x37, 0x83, 0xC6, 0xDF, 0xD4, 0xCC, 0xB3, 0xFF, 0xFF, 0xFE, 0xF0, 0xB8, 0xD6,
+ 0xB8, 0xCB, 0xE2, 0xB6, 0x47))
+
++ def testManySingleByteArcs(self):
++ # 4096 arcs, length encoded long-form as 0x82 0x10 0x01
++ arcCount = 4096
++ substrate = encoder.encode(
++ univ.ObjectIdentifier((1, 3) + (1,) * arcCount)
++ )
++
++ assert substrate == ints2octs(
++ [0x06, 0x82, 0x10, 0x01, 0x2B] + [0x01] * arcCount)
++
+
+ class ObjectIdentifierWithSchemaEncoderTestCase(BaseTestCase):
+ def testOne(self):
diff --git a/python-pyasn1-CVE-2026-59886.patch b/python-pyasn1-CVE-2026-59886.patch
new file mode 100644
index 0000000..9920589
--- /dev/null
+++ b/python-pyasn1-CVE-2026-59886.patch
@@ -0,0 +1,122 @@
+Backport of the Real.__float__() overflow guard, fixing CVE-2026-59886.
+A base-10 Real carrying a huge exponent made pow() materialize an astronomically
+large integer; base 2 now goes through math.ldexp and base 10 refuses exponents
+past sys.float_info.max_10_exp. __normalizeBase10() also switches to integer
+division, which float division could not represent past a 2**53 mantissa.
+Upstream's unrelated Real round-trip tests from the same commit are left out.
+https://github.com/pyasn1/pyasn1/commit/e60c691cb91addb8fcefa2f537e85ede6fb1e886
+
+--- a/pyasn1/type/univ.py
++++ b/pyasn1/type/univ.py
+@@ -1318,7 +1318,7 @@
+ def __normalizeBase10(value):
+ m, b, e = value
+ while m and m % 10 == 0:
+- m /= 10
++ m //= 10
+ e += 1
+ return m, b, e
+
+@@ -1457,10 +1457,21 @@
+ 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)))
+--- a/tests/codec/ber/test_decoder.py
++++ b/tests/codec/ber/test_decoder.py
+@@ -565,6 +565,22 @@
+ ints2octs((9, 4, 161, 255, 1, 3))
+ ) == (univ.Real((3, 2, -1020)), null)
+
++ def testLargeBinaryPrettyPrintOverflow(self):
++ value, rest = decoder.decode(
++ str2octs('\t\t\xeb\x060662.666\xd0B\x00\x00\x00\x00\x00\x00\x00')
++ )
++
++ assert value.prettyPrint() == '<overflow>'
++ assert rest == str2octs('6\xd0B\x00\x00\x00\x00\x00\x00\x00')
++
++ try:
++ float(value)
++ except OverflowError:
++ pass
++ else:
++ assert 0, '__float__() tolerated overflow'
++
++
+ # TODO: this requires Real type comparison fix
+
+ # def testBin6(self):
+--- a/tests/type/test_univ.py
++++ b/tests/type/test_univ.py
+@@ -780,9 +780,49 @@
+ 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'
================================================================
---- gitweb:
http://git.pld-linux.org/gitweb.cgi/packages/python-pyasn1.git/commitdiff/99caa779053f98be005a90fe9ebc71ec522583da
More information about the pld-cvs-commit
mailing list