@@ -20,6 +20,7 @@ SRC_URI += " \
file://run-ptest \
file://CVE-2026-23490.patch \
file://CVE-2026-59886.patch \
+ file://CVE-2026-59884.patch \
"
RDEPENDS:${PN}-ptest += " \
new file mode 100644
@@ -0,0 +1,245 @@
+From 38e8ae286160eb27620e7cb42108b3b28d1f299f 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
+
+CVE: CVE-2026-59884
+Upstream-Status: Backport [https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5]
+
+(cherry picked from commit 628e36ecbb5277a3f01572ce418ef54271b165a5)
+Signed-off-by: Emily Vekariya <evekariy@cisco.com>
+---
+ 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(-)
+
+diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py
+index be8ba65..18865c2 100644
+--- a/pyasn1/codec/ber/decoder.py
++++ b/pyasn1/codec/ber/decoder.py
+@@ -39,6 +39,10 @@ SubstrateUnderrunError = error.SubstrateUnderrunError
+ # 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
++
+
+ class AbstractPayloadDecoder(object):
+ protoComponent = None
+@@ -1570,7 +1574,7 @@ class SingleItemDecoder(object):
+
+ if tagId == 0x1F:
+ isShortTag = False
+- lengthOctetIdx = 0
++ tagOctetCount = 0
+ tagId = 0
+
+ while True:
+@@ -1584,7 +1588,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 a21a405..bbbdd85 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 f6ff7b0..0152027 100644
+--- a/tests/codec/ber/test_decoder.py
++++ b/tests/codec/ber/test_decoder.py
+@@ -34,6 +34,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 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(
+diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py
+index 3d27194..d759f76 100644
+--- a/tests/codec/cer/test_decoder.py
++++ b/tests/codec/cer/test_decoder.py
+@@ -67,6 +67,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 RealDecoderTestCase(BaseTestCase):
+ def testLargeBinaryRoundTrip(self):
+ substrate = encoder.encode(univ.Real((-1, 2, 76354972)))
+diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py
+index 553563c..726c999 100644
+--- a/tests/codec/der/test_decoder.py
++++ b/tests/codec/der/test_decoder.py
+@@ -73,6 +73,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 RealDecoderTestCase(BaseTestCase):
+ def testCanonicalLargeBinaryReal(self):
+ substrate = encoder.encode(univ.Real((1, 2, 1000000)))
+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):
+--
+2.34.1
+