From 9b89b511a7284f17ef3a2de6d05fbf6030133abb 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 CVE: CVE-2026-59886 Upstream-Status: Backport [https://github.com/pyasn1/pyasn1/commit/e60c691cb91addb8fcefa2f537e85ede6fb1e886] (cherry picked from commit e60c691cb91addb8fcefa2f537e85ede6fb1e886) Signed-off-by: Emily Vekariya --- pyasn1/type/univ.py | 21 +++++++++---- tests/codec/ber/test_decoder.py | 53 +++++++++++++++++++++++++++------ tests/codec/cer/test_decoder.py | 10 +++++++ tests/codec/der/test_decoder.py | 19 ++++++++++++ tests/type/test_univ.py | 40 +++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 14 deletions(-) diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index c5d0778..adff2df 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1318,7 +1318,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 @@ -1457,10 +1457,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 f033dfd..f6ff7b0 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.compat.octets import ints2octs, str2octs, null from pyasn1 import error @@ -547,17 +548,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)) + ) -# def testBin6(self): -# assert decoder.decode( -# ints2octs((9, 5, 162, 0, 255, 255, 1)) -# ) == (univ.Real((1, 2, 262140)), null) + 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)) + ) -# def testBin7(self): -# assert decoder.decode( -# ints2octs((9, 7, 227, 4, 1, 35, 69, 103, 1)) -# ) == (univ.Real((-1, 2, 76354972)), null) + assert tuple(value) == (1, 2, 1000000) + assert rest == b'' + + def testLargeBinaryPrettyPrintOverflow(self): + value, rest = decoder.decode( + b'\t\t\xeb\x060662.666\xd0B\x00\x00\x00\x00\x00\x00\x00' + ) + + assert value.prettyPrint() == '' + assert rest == b'6\xd0B\x00\x00\x00\x00\x00\x00\x00' + + try: + float(value) + except OverflowError: + pass + else: + assert 0, '__float__() tolerated overflow' def testPlusInf(self): assert decoder.decode( diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py index 133affd..3d27194 100644 --- a/tests/codec/cer/test_decoder.py +++ b/tests/codec/cer/test_decoder.py @@ -15,6 +15,7 @@ from pyasn1.type import opentype from pyasn1.type import univ from pyasn1.codec.cer import decoder from pyasn1.compat.octets import ints2octs, str2octs, null +from pyasn1.codec.cer import encoder from pyasn1.error import PyAsn1Error @@ -66,6 +67,15 @@ class OctetStringDecoderTestCase(BaseTestCase): # TODO: test failures on short chunked and long unchunked substrate samples +class RealDecoderTestCase(BaseTestCase): + def testLargeBinaryRoundTrip(self): + substrate = encoder.encode(univ.Real((-1, 2, 76354972))) + value, rest = decoder.decode(substrate) + + assert tuple(value) == (-1, 2, 76354972) + assert rest == b'' + + class SequenceDecoderWithUntaggedOpenTypesTestCase(BaseTestCase): def setUp(self): openType = opentype.OpenType( diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py index 5bc9deb..553563c 100644 --- a/tests/codec/der/test_decoder.py +++ b/tests/codec/der/test_decoder.py @@ -15,6 +15,7 @@ from pyasn1.type import opentype from pyasn1.type import univ from pyasn1.codec.der import decoder from pyasn1.compat.octets import ints2octs, null +from pyasn1.codec.der import encoder from pyasn1.error import PyAsn1Error @@ -72,6 +73,24 @@ class OctetStringDecoderTestCase(BaseTestCase): assert 0, 'chunked encoding tolerated' +class RealDecoderTestCase(BaseTestCase): + def testCanonicalLargeBinaryReal(self): + substrate = encoder.encode(univ.Real((1, 2, 1000000))) + assert substrate == bytes((9, 5, 0x82, 0x0f, 0x42, 0x40, 1)) + + value, rest = decoder.decode(substrate) + + assert tuple(value) == (1, 2, 1000000) + assert rest == b'' + + def testLargeBinaryRoundTrip(self): + substrate = encoder.encode(univ.Real((-1, 2, 76354972))) + value, rest = decoder.decode(substrate) + + assert tuple(value) == (-1, 2, 76354972) + assert rest == b'' + + class SequenceDecoderWithUntaggedOpenTypesTestCase(BaseTestCase): def setUp(self): openType = opentype.OpenType( diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index 8aec183..bc21c37 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -780,9 +780,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.34.1