Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions SPECS/python-pyasn1/CVE-2026-59884.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
From 628e36ecbb5277a3f01572ce418ef54271b165a5 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <simon.pichugin@gmail.com>
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 | 7 +++++++
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, 101 insertions(+), 4 deletions(-)

diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py
index 1844811..950b6b4 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


@@ -1373,6 +1377,9 @@ class Decoder(object):
while True:
integerTag = oct2int(substrate[lengthOctetIdx])
lengthOctetIdx += 1
+
+ if lengthOctetIdx > 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:
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

Loading
Loading