diff --git a/CVE-2026-59884.patch b/CVE-2026-59884.patch new file mode 100644 index 0000000000000000000000000000000000000000..74fbd21e9e628d1b17a047e7691b828e855ca1be --- /dev/null +++ b/CVE-2026-59884.patch @@ -0,0 +1,261 @@ +From 628e36ecbb5277a3f01572ce418ef54271b165a5 Mon Sep 17 00:00:00 2001 +From: Simon Pichugin +Date: Wed, 8 Jul 2026 17:36:30 -0700 +Subject: [PATCH] Merge commit from fork +Adapted-by: PengLAI Code +--- + pyasn1/codec/ber/decoder.py | 13 +++++++++++-- + pyasn1/type/tag.py | 20 ++++++++++++++++---- + tests/codec/ber/test_decoder.py | 26 ++++++++++++++++++++++++++ + tests/codec/cer/test_decoder.py | 16 ++++++++++++++++ + tests/codec/der/test_decoder.py | 16 ++++++++++++++++ + tests/type/test_tag.py | 20 ++++++++++++++++++++ + 6 files changed, 105 insertions(+), 6 deletions(-) + +diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py +index e274c38..56f2860 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 + + class AbstractPayloadDecoder(object): +@@ -1629,7 +1633,7 @@ class SingleItemDecoder(object): + + if tagId == 0x1F: + isShortTag = False +- lengthOctetIdx = 0 ++ tagOctetCount = 0 + tagId = 0 + + while True: +@@ -1643,7 +1647,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 3e0e09a..5b30558 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 + +@@ -32,6 +33,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 24d1999..92affb0 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,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 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..a89e909 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,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 SequenceDecoderWithUntaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + openType = opentype.OpenType( +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 --git a/python-pyasn1.spec b/python-pyasn1.spec index 0ee52d0826de2de564297b2e340d9b34de3d782b..f3fbcca797e33791c6e37f316e1a9dfc894c2b48 100644 --- a/python-pyasn1.spec +++ b/python-pyasn1.spec @@ -1,4 +1,4 @@ -%define anolis_release 3 +%define anolis_release 4 %global module pyasn1 %global modules_version 0.4.2 @@ -16,6 +16,8 @@ BuildRequires: python3-wheel # https://github.com/pyasn1/pyasn1/commit/5a49bd1fe93b5b866a1210f6bf0a3924f21572c8 Patch0001: 0001-add-patch-to-fix-CVE-2026-30922.patch +# https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5 +Patch0002: CVE-2026-59884.patch %description This is an implementation of ASN.1 types and codecs in the Python programming @@ -54,6 +56,7 @@ Requires: python3-pyasn1 = %{EVR} %prep %setup -n %{module}-%{version} -q -b1 %patch01 -p1 +%patch02 -p1 %build %{__python3} -m pip wheel --no-build-isolation --no-deps --wheel-dir %{_builddir}/pyasn1-wheels . @@ -95,6 +98,9 @@ PYTHONPATH=%{buildroot}%{python3_sitelib} %{__python3} -m pytest tests/ -v %doc docs/build/html/* %changelog +* Sat Aug 29 2026 PengLAI Code - 0.6.2-4 +- Add patch to fix CVE-2026-59884 + * Tue Mar 24 2026 lzq11122 - 0.6.2-3 - Add patch to fix CVE-2026-30922