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

---
 pyasn1/codec/ber/decoder.py     | 13 +++++++++++--
 pyasn1/type/tag.py              | 20 ++++++++++++++++----
 tests/codec/ber/test_decoder.py | 25 +++++++++++++++++++++++++
 tests/codec/cer/test_decoder.py | 15 +++++++++++++++
 tests/codec/der/test_decoder.py | 15 +++++++++++++++
 tests/type/test_tag.py          | 20 ++++++++++++++++++++
 6 files changed, 102 insertions(+), 6 deletions(-)

Index: pyasn1-0.5.0/pyasn1/codec/ber/decoder.py
===================================================================
--- pyasn1-0.5.0.orig/pyasn1/codec/ber/decoder.py
+++ pyasn1-0.5.0/pyasn1/codec/ber/decoder.py
@@ -38,6 +38,10 @@ MAX_OID_ARC_CONTINUATION_OCTETS = 20
 MAX_NESTING_DEPTH = 100
 
 
+# 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
+
 class AbstractPayloadDecoder(object):
     protoComponent = None
 
@@ -1577,7 +1581,7 @@ class SingleItemDecoder(object):
 
                     if tagId == 0x1F:
                         isShortTag = False
-                        lengthOctetIdx = 0
+                        tagOctetCount = 0
                         tagId = 0
 
                         while True:
@@ -1591,7 +1595,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)
 
Index: pyasn1-0.5.0/pyasn1/type/tag.py
===================================================================
--- pyasn1-0.5.0.orig/pyasn1/type/tag.py
+++ pyasn1-0.5.0/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:
Index: pyasn1-0.5.0/tests/codec/ber/test_decoder.py
===================================================================
--- pyasn1-0.5.0.orig/tests/codec/ber/test_decoder.py
+++ pyasn1-0.5.0/tests/codec/ber/test_decoder.py
@@ -33,6 +33,31 @@ class LargeTagDecoderTestCase(BaseTestCa
     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 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(
Index: pyasn1-0.5.0/tests/codec/cer/test_decoder.py
===================================================================
--- pyasn1-0.5.0.orig/tests/codec/cer/test_decoder.py
+++ pyasn1-0.5.0/tests/codec/cer/test_decoder.py
@@ -66,6 +66,21 @@ class OctetStringDecoderTestCase(BaseTes
     # 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(
Index: pyasn1-0.5.0/tests/codec/der/test_decoder.py
===================================================================
--- pyasn1-0.5.0.orig/tests/codec/der/test_decoder.py
+++ pyasn1-0.5.0/tests/codec/der/test_decoder.py
@@ -72,6 +72,21 @@ class OctetStringDecoderTestCase(BaseTes
             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(
Index: pyasn1-0.5.0/tests/type/test_tag.py
===================================================================
--- pyasn1-0.5.0.orig/tests/type/test_tag.py
+++ pyasn1-0.5.0/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(TagSetTestCaseB
     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):
