[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