diff --git a/SPECS/python-pyasn1/CVE-2026-30922.patch b/SPECS/python-pyasn1/CVE-2026-30922.patch index 131460f0f45..9c706385d37 100644 --- a/SPECS/python-pyasn1/CVE-2026-30922.patch +++ b/SPECS/python-pyasn1/CVE-2026-30922.patch @@ -7,15 +7,16 @@ Subject: [PATCH] BER decoder: enforce maximum nesting depth to prevent Signed-off-by: Azure Linux Security Servicing Account Upstream-reference: AI Backport of https://github.com/pyasn1/pyasn1/commit/25ad481c19fdb006e20485ef3fc2e5b3eff30ef0.patch + --- - pyasn1/codec/ber/decoder.py | 12 ++++ - tests/codec/ber/test_decoder.py | 117 ++++++++++++++++++++++++++++++++ + pyasn1/codec/ber/decoder.py | 14 ++++ + tests/codec/ber/test_decoder.py | 116 ++++++++++++++++++++++++++++++++ tests/codec/cer/test_decoder.py | 24 +++++++ - tests/codec/der/test_decoder.py | 43 ++++++++++++ + tests/codec/der/test_decoder.py | 42 ++++++++++++ 4 files changed, 196 insertions(+) diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py -index 5ff485f..1844811 100644 +index 5ff485f..49c0ed6 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -23,6 +23,10 @@ LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_DECODER) @@ -29,31 +30,31 @@ index 5ff485f..1844811 100644 class AbstractDecoder(object): protoComponent = None -@@ -1312,6 +1316,14 @@ class Decoder(object): +@@ -1312,6 +1316,16 @@ class Decoder(object): if LOG: LOG('decoder called at scope %s with state %d, working with up to %d octets of substrate: %s' % (debug.scope, state, len(substrate), debug.hexdump(substrate))) + + _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) # Look for end-of-octets sentinel diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py -index e3b74df..f2a5975 100644 +index e3b74df..450e78d 100644 --- a/tests/codec/ber/test_decoder.py +++ b/tests/codec/ber/test_decoder.py -@@ -1614,6 +1614,123 @@ class ErrorOnDecodingTestCase(BaseTestCase): +@@ -1614,6 +1614,122 @@ class ErrorOnDecodingTestCase(BaseTestCase): 'Unexpected rest of substrate after raw dump %r' % rest) -+ -+ +class NestingDepthLimitTestCase(BaseTestCase): + """Test protection against deeply nested ASN.1 structures (CVE prevention).""" + @@ -168,19 +169,19 @@ index e3b74df..f2a5975 100644 + pass + else: + assert False, 'Deeply nested with schema not rejected' ++ + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py -index bb5ce93..5064f92 100644 +index bb5ce93..50facd5 100644 --- a/tests/codec/cer/test_decoder.py +++ b/tests/codec/cer/test_decoder.py @@ -367,6 +367,30 @@ class SequenceDecoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): assert s[1][0] == univ.OctetString(hexValue='02010C') -+ +class NestingDepthLimitTestCase(BaseTestCase): + """Test CER decoder protection against deeply nested structures.""" + @@ -203,19 +204,19 @@ index bb5ce93..5064f92 100644 + pass + except RecursionError: + assert False, 'Got RecursionError instead of PyAsn1Error' ++ + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py -index 51ce296..53ffd9a 100644 +index 51ce296..ae4416d 100644 --- a/tests/codec/der/test_decoder.py +++ b/tests/codec/der/test_decoder.py -@@ -119,6 +119,49 @@ class SequenceDecoderWithUntaggedOpenTypesTestCase(BaseTestCase): - except PyAsn1Error: - pass +@@ -367,6 +367,48 @@ class SequenceDecoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): + assert s[1][0] == univ.OctetString(hexValue='02010C') + -+ +class NestingDepthLimitTestCase(BaseTestCase): + """Test DER decoder protection against deeply nested structures.""" + @@ -258,9 +259,9 @@ index 51ce296..53ffd9a 100644 + assert False, 'Got RecursionError instead of PyAsn1Error' + + - else: - assert False, 'unknown open type tolerated' + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + if __name__ == '__main__': -- -2.45.4 +2.34.1 diff --git a/SPECS/python-pyasn1/CVE-2026-59884.patch b/SPECS/python-pyasn1/CVE-2026-59884.patch new file mode 100644 index 00000000000..621d5c777ba --- /dev/null +++ b/SPECS/python-pyasn1/CVE-2026-59884.patch @@ -0,0 +1,271 @@ +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 + +Upstream Patch reference: https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5.patch +--- + pyasn1/codec/ber/decoder.py | 17 +++++++++++++---- + 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, 107 insertions(+), 8 deletions(-) + +diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py +index 1844811..2cd8a82 100644 +--- a/pyasn1/codec/ber/decoder.py ++++ b/pyasn1/codec/ber/decoder.py +@@ -24,6 +24,10 @@ noValue = base.noValue + + + # Maximum nesting depth to protect against excessively nested ASN.1 structures ++ ++# 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 + + +@@ -1366,19 +1370,24 @@ class Decoder(object): + + if tagId == 0x1F: + isShortTag = False +- lengthOctetIdx = 0 ++ tagOctetCount = 0 + tagId = 0 + + try: + while True: +- integerTag = oct2int(substrate[lengthOctetIdx]) +- lengthOctetIdx += 1 ++ integerTag = oct2int(substrate[tagOctetCount]) ++ 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) + if not integerTag & 0x80: + break + +- substrate = substrate[lengthOctetIdx:] ++ substrate = substrate[tagOctetCount:] + + except IndexError: + raise error.SubstrateUnderrunError( +diff --git a/pyasn1/type/tag.py b/pyasn1/type/tag.py +index b88a734..a9811c4 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 f2a5975..8c11e26 100644 +--- a/tests/codec/ber/test_decoder.py ++++ b/tests/codec/ber/test_decoder.py +@@ -20,6 +20,7 @@ from pyasn1.type import opentype + from pyasn1.type import univ + from pyasn1.type import char + 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.error import PyAsn1Error +@@ -32,6 +33,31 @@ class LargeTagDecoderTestCase(BaseTestCase): + 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 == 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 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( +diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py +index 5064f92..927e1f4 100644 +--- a/tests/codec/cer/test_decoder.py ++++ b/tests/codec/cer/test_decoder.py +@@ -18,6 +18,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.compat.octets import ints2octs, str2octs, null + from pyasn1.error import PyAsn1Error + +@@ -69,6 +70,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 53ffd9a..c2908c0 100644 +--- a/tests/codec/der/test_decoder.py ++++ b/tests/codec/der/test_decoder.py +@@ -19,6 +19,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.compat.octets import ints2octs, null + from pyasn1.error import PyAsn1Error + +@@ -77,6 +78,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 e8dd7a3..d737c94 100644 +--- a/tests/type/test_tag.py ++++ b/tests/type/test_tag.py +@@ -14,6 +14,7 @@ except ImportError: + + from tests.base import BaseTestCase + ++from pyasn1 import error + from pyasn1.type import tag + + +@@ -28,6 +29,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): +@@ -59,6 +73,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): +-- +2.43.0 + diff --git a/SPECS/python-pyasn1/CVE-2026-59885.patch b/SPECS/python-pyasn1/CVE-2026-59885.patch new file mode 100644 index 00000000000..0da0a2031f2 --- /dev/null +++ b/SPECS/python-pyasn1/CVE-2026-59885.patch @@ -0,0 +1,175 @@ +From 45bdb19eb7df4b3780fe9c912c63e99bffc39dd9 Mon Sep 17 00:00:00 2001 +From: Simon Pichugin +Date: Wed, 8 Jul 2026 17:37:40 -0700 +Subject: [PATCH] Merge commit from fork + +Upstream Patch reference: https://github.com/pyasn1/pyasn1/commit/45bdb19eb7df4b3780fe9c912c63e99bffc39dd9.patch +--- + pyasn1/codec/ber/decoder.py | 18 ++++++++++-------- + pyasn1/codec/ber/encoder.py | 12 ++++++------ + tests/codec/ber/test_decoder.py | 22 ++++++++++++++++++++++ + tests/codec/ber/test_encoder.py | 10 ++++++++++ + 4 files changed, 48 insertions(+), 14 deletions(-) + +diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py +index 950b6b4..50c8975 100644 +--- a/pyasn1/codec/ber/decoder.py ++++ b/pyasn1/codec/ber/decoder.py +@@ -338,14 +338,14 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder): + + head = octs2ints(head) + +- oid = () ++ oid = [] + index = 0 + substrateLen = len(head) + while index < substrateLen: + subId = head[index] + index += 1 + if subId < 128: +- oid += (subId,) ++ oid.append(subId) + elif subId > 128: + # Construct subid from a number of octets + nextSubId = subId +@@ -354,11 +354,11 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder): + 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 = head[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 +@@ -368,15 +368,17 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder): + + # 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' % head[0]) + +- return self._createComponent(asn1Spec, tagSet, oid, **options), tail ++ return self._createComponent(asn1Spec, tagSet, tuple(oid), **options), tail + + + class RealDecoder(AbstractSimpleDecoder): +diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py +index 778aa86..c02b6c0 100644 +--- a/pyasn1/codec/ber/encoder.py ++++ b/pyasn1/codec/ber/encoder.py +@@ -326,30 +326,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 RealEncoder(AbstractItemEncoder): +diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py +index 8c11e26..64d0780 100644 +--- a/tests/codec/ber/test_decoder.py ++++ b/tests/codec/ber/test_decoder.py +@@ -26,6 +26,14 @@ from pyasn1.compat.octets import ints2octs, str2octs, null + from pyasn1.error import PyAsn1Error + + ++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(ints2octs((127, 141, 245, 182, 253, 47, 3, 2, 1, 1))) == (1, null) +@@ -433,6 +441,20 @@ class ObjectIdentifierDecoderTestCase(BaseTestCase): + 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): ++ 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) ++ + + class RealDecoderTestCase(BaseTestCase): + def testChar(self): +diff --git a/tests/codec/ber/test_encoder.py b/tests/codec/ber/test_encoder.py +index df82e7b..88b3876 100644 +--- a/tests/codec/ber/test_encoder.py ++++ b/tests/codec/ber/test_encoder.py +@@ -354,6 +354,16 @@ class ObjectIdentifierEncoderTestCase(BaseTestCase): + ) == 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): ++ 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): +-- +2.43.0 + diff --git a/SPECS/python-pyasn1/CVE-2026-59886.patch b/SPECS/python-pyasn1/CVE-2026-59886.patch new file mode 100644 index 00000000000..95b46b5ca69 --- /dev/null +++ b/SPECS/python-pyasn1/CVE-2026-59886.patch @@ -0,0 +1,224 @@ +From e60c691cb91addb8fcefa2f537e85ede6fb1e886 Mon Sep 17 00:00:00 2001 +From: Simon Pichugin +Date: Wed, 8 Jul 2026 17:32:09 -0700 +Subject: [PATCH] Merge commit from fork + +Upstream Patch reference: https://github.com/pyasn1/pyasn1/commit/e60c691cb91addb8fcefa2f537e85ede6fb1e886.patch +--- + pyasn1/type/univ.py | 21 +++++++++---- + tests/codec/ber/test_decoder.py | 52 +++++++++++++++++++++++++++------ + tests/codec/cer/test_decoder.py | 9 ++++++ + tests/codec/der/test_decoder.py | 18 ++++++++++++ + tests/type/test_univ.py | 40 +++++++++++++++++++++++++ + 5 files changed, 126 insertions(+), 14 deletions(-) + +diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py +index aa688b2..7a153b6 100644 +--- a/pyasn1/type/univ.py ++++ b/pyasn1/type/univ.py +@@ -1333,7 +1333,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 + +@@ -1472,10 +1472,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 64d0780..1c0246e 100644 +--- a/tests/codec/ber/test_decoder.py ++++ b/tests/codec/ber/test_decoder.py +@@ -487,17 +487,51 @@ class RealDecoderTestCase(BaseTestCase): + ints2octs((9, 4, 161, 255, 1, 3)) + ) == (univ.Real((3, 2, -1020)), null) + +-# 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)) ++ ) ++ ++ 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) ++ ++ 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 testBin6(self): +-# assert decoder.decode( +-# ints2octs((9, 5, 162, 0, 255, 255, 1)) +-# ) == (univ.Real((1, 2, 262140)), null) ++ def testLargeBinaryPrettyPrintOverflow(self): ++ value, rest = decoder.decode( ++ b'\t\t\xeb\x060662.666\xd0B\x00\x00\x00\x00\x00\x00\x00' ++ ) ++ ++ assert value.prettyPrint() == '' ++ assert rest == b'6\xd0B\x00\x00\x00\x00\x00\x00\x00' + +-# def testBin7(self): +-# assert decoder.decode( +-# ints2octs((9, 7, 227, 4, 1, 35, 69, 103, 1)) +-# ) == (univ.Real((-1, 2, 76354972)), null) ++ 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 927e1f4..f910313 100644 +--- a/tests/codec/cer/test_decoder.py ++++ b/tests/codec/cer/test_decoder.py +@@ -56,6 +56,15 @@ class BitStringDecoderTestCase(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 OctetStringDecoderTestCase(BaseTestCase): + def testShortMode(self): + assert decoder.decode( +diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py +index c2908c0..6809d12 100644 +--- a/tests/codec/der/test_decoder.py ++++ b/tests/codec/der/test_decoder.py +@@ -93,6 +93,24 @@ class LargeTagDecoderTestCase(BaseTestCase): + assert 0, 'excessive long tag 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 124e5e9..ba65eae 100644 +--- a/tests/type/test_univ.py ++++ b/tests/type/test_univ.py +@@ -803,9 +803,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() == '' ++ ++ 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' +-- +2.43.0 + diff --git a/SPECS/python-pyasn1/python-pyasn1.spec b/SPECS/python-pyasn1/python-pyasn1.spec index 7e72eceaae1..17819d0a061 100644 --- a/SPECS/python-pyasn1/python-pyasn1.spec +++ b/SPECS/python-pyasn1/python-pyasn1.spec @@ -1,7 +1,7 @@ Summary: Implementation of ASN.1 types and codecs in Python programming language Name: python-pyasn1 Version: 0.4.8 -Release: 2%{?dist} +Release: 3%{?dist} License: BSD Vendor: Microsoft Corporation Distribution: Azure Linux @@ -9,6 +9,9 @@ Group: Development/Languages/Python URL: https://pypi.org/project/pyasn1 Source0: https://files.pythonhosted.org/packages/source/p/pyasn1/pyasn1-%{version}.tar.gz Patch0: CVE-2026-30922.patch +Patch1: CVE-2026-59884.patch +Patch2: CVE-2026-59885.patch +Patch3: CVE-2026-59886.patch BuildArch: noarch %description @@ -42,6 +45,9 @@ to be suitable for a wide range of protocols based on ASN.1 specification. %{python3_sitelib}/* %changelog +* Fri Jul 17 2026 Azure Linux Security Servicing Account - 0.4.8-3 +- Patch for CVE-2026-59886, CVE-2026-59885, CVE-2026-59884 + * Wed Mar 25 2026 Azure Linux Security Servicing Account - 0.4.8-2 - Patch for CVE-2026-30922