123 lines
5.1 KiB
Diff
123 lines
5.1 KiB
Diff
From b111e63bd3b86ca9747b1bbb3f7eae7b9c01a82e Mon Sep 17 00:00:00 2001
|
|
From: Masahiro Matsuya <mmatsuya@redhat.com>
|
|
Date: Wed, 18 Feb 2026 13:54:42 +0900
|
|
Subject: [PATCH] Backport commit be353d7
|
|
|
|
Add limit of 20 continuation octets per OID arc to prevent a potential memory
|
|
exhaustion from excessive continuation bytes input.
|
|
---
|
|
pyasn1/codec/ber/decoder.py | 11 ++++++
|
|
tests/codec/ber/test_decoder.py | 66 +++++++++++++++++++++++++++++++++
|
|
2 files changed, 77 insertions(+)
|
|
|
|
diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py
|
|
index ee3064f..69c9ecd 100644
|
|
--- a/pyasn1/codec/ber/decoder.py
|
|
+++ b/pyasn1/codec/ber/decoder.py
|
|
@@ -14,6 +14,10 @@ __all__ = ['decode']
|
|
|
|
noValue = base.noValue
|
|
|
|
+# Maximum number of continuation octets (high-bit set) allowed per OID arc.
|
|
+# 20 octets allows up to 140-bit integers, supporting UUID-based OIDs
|
|
+MAX_OID_ARC_CONTINUATION_OCTETS = 20
|
|
+
|
|
|
|
class AbstractDecoder(object):
|
|
protoComponent = None
|
|
@@ -284,7 +288,14 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder):
|
|
# Construct subid from a number of octets
|
|
nextSubId = subId
|
|
subId = 0
|
|
+ continuationOctetCount = 0
|
|
while nextSubId >= 128:
|
|
+ continuationOctetCount += 1
|
|
+ if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS:
|
|
+ raise error.PyAsn1Error(
|
|
+ 'OID arc exceeds maximum continuation octets limit (%d) '
|
|
+ 'at position %d' % (MAX_OID_ARC_CONTINUATION_OCTETS, index)
|
|
+ )
|
|
subId = (subId << 7) + (nextSubId & 0x7F)
|
|
if index >= substrateLen:
|
|
raise error.SubstrateUnderrunError(
|
|
diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py
|
|
index 5ec3a5f..ba7dfec 100644
|
|
--- a/tests/codec/ber/test_decoder.py
|
|
+++ b/tests/codec/ber/test_decoder.py
|
|
@@ -403,6 +403,72 @@ class ObjectIdentifierDecoderTestCase(BaseTestCase):
|
|
0xB8, 0xCB, 0xE2, 0xB6, 0x47))
|
|
) == ((2, 999, 18446744073709551535184467440737095), null)
|
|
|
|
+ def testExcessiveContinuationOctets(self):
|
|
+ """Test that OID arcs with excessive continuation octets are rejected."""
|
|
+ # Create a payload with 25 continuation octets (exceeds 20 limit)
|
|
+ # 0x81 bytes are continuation octets, 0x01 terminates
|
|
+ malicious_payload = bytes([0x06, 26]) + bytes([0x81] * 25) + bytes([0x01])
|
|
+ try:
|
|
+ decoder.decode(malicious_payload)
|
|
+ except PyAsn1Error:
|
|
+ pass
|
|
+ else:
|
|
+ assert 0, 'Excessive continuation octets tolerated'
|
|
+
|
|
+ def testMaxAllowedContinuationOctets(self):
|
|
+ """Test that OID arcs at the maximum continuation octets limit work."""
|
|
+ # Create a payload with exactly 20 continuation octets (at limit)
|
|
+ # This should succeed
|
|
+ payload = bytes([0x06, 21]) + bytes([0x81] * 20) + bytes([0x01])
|
|
+ try:
|
|
+ decoder.decode(payload)
|
|
+ except PyAsn1Error:
|
|
+ assert 0, 'Valid OID with 20 continuation octets rejected'
|
|
+
|
|
+ def testOneOverContinuationLimit(self):
|
|
+ """Test boundary: 21 continuation octets (one over limit) is rejected."""
|
|
+ payload = bytes([0x06, 22]) + bytes([0x81] * 21) + bytes([0x01])
|
|
+ try:
|
|
+ decoder.decode(payload)
|
|
+ except PyAsn1Error:
|
|
+ pass
|
|
+ else:
|
|
+ assert 0, '21 continuation octets tolerated (should be rejected)'
|
|
+
|
|
+ def testExcessiveContinuationInSecondArc(self):
|
|
+ """Test that limit applies to subsequent arcs, not just the first."""
|
|
+ # First arc: valid simple byte (0x55 = 85, decodes to arc 2.5)
|
|
+ # Second arc: excessive continuation octets
|
|
+ payload = bytes([0x06, 27]) + bytes([0x55]) + bytes([0x81] * 25) + bytes([0x01])
|
|
+ try:
|
|
+ decoder.decode(payload)
|
|
+ except PyAsn1Error:
|
|
+ pass
|
|
+ else:
|
|
+ assert 0, 'Excessive continuation in second arc tolerated'
|
|
+
|
|
+ def testMultipleArcsAtLimit(self):
|
|
+ """Test multiple arcs each at the continuation limit work correctly."""
|
|
+ # Two arcs, each with 20 continuation octets (both at limit)
|
|
+ arc1 = bytes([0x81] * 20) + bytes([0x01]) # 21 bytes
|
|
+ arc2 = bytes([0x81] * 20) + bytes([0x01]) # 21 bytes
|
|
+ payload = bytes([0x06, 42]) + arc1 + arc2
|
|
+ try:
|
|
+ decoder.decode(payload)
|
|
+ except PyAsn1Error:
|
|
+ assert 0, 'Multiple valid arcs at limit rejected'
|
|
+
|
|
+ def testExcessiveContinuationWithMaxBytes(self):
|
|
+ """Test with 0xFF continuation bytes (maximum value, not just 0x81)."""
|
|
+ # 0xFF bytes are also continuation octets (high bit set)
|
|
+ malicious_payload = bytes([0x06, 26]) + bytes([0xFF] * 25) + bytes([0x01])
|
|
+ try:
|
|
+ decoder.decode(malicious_payload)
|
|
+ except PyAsn1Error:
|
|
+ pass
|
|
+ else:
|
|
+ assert 0, 'Excessive 0xFF continuation octets tolerated'
|
|
+
|
|
|
|
class RealDecoderTestCase(BaseTestCase):
|
|
def testChar(self):
|
|
--
|
|
2.52.0
|
|
|