Skip to content

pyasn1 has quadratic-time denial of service in constructed string and indefinite-length ANY decoding

High severity GitHub Reviewed Published Oct 27, 2026 in pyasn1/pyasn1 • Updated Oct 30, 2026

Package

pip pyasn1 (pip)

Affected versions

<= 0.6.4

Patched versions

0.6.5

Description

Summary

In pyasn1 0.6.4 and earlier, decoding a constructed OCTET STRING or character string made of many fragments can spend disproportionate CPU time repeatedly copying the accumulated value. A separate indefinite-length ANY path has the same problem while collecting the encoded components. The work grows roughly quadratically with the number of fixed-size fragments, although the input and decoded value grow linearly. An application that lets an attacker supply such ASN.1 input to an affected decoder can suffer CPU exhaustion.

The affected string paths are available in BER and CER. The DER decoder also accepts certain non-DER constructed strings: a definite-length constructed OCTET STRING when an asn1Spec selects univ.OctetString, and constructed character strings such as UTF8String even without a schema. Indefinite-length ANY is affected in BER and CER when an asn1Spec selects univ.Any; DER rejects indefinite lengths.

This is a distinct decoder path from the OBJECT IDENTIFIER/RELATIVE-OID accumulation addressed by CVE-2026-59885. It has the same complexity class, but the OID fix in 0.6.4 did not change these string or ANY loops.

Technical details

In the v0.6.4 BER decoder, OctetStringPayloadDecoder appends each decoded fragment to a growing bytes value with header += component in both its definite-length and indefinite-length constructed forms. AnyPayloadDecoder.indefLenValueDecoder similarly uses chunk += component. In the exercised paths, each concatenation copies an increasingly large prefix. For N fixed-size fragments, the total copied data grows with 1 + 2 + ... + N, or O(N²).

The OCTET STRING decoder is also the base class of the decoders for UTF8String, NumericString, PrintableString, TeletexString, VideotexString, IA5String, GraphicString, VisibleString, GeneralString, UniversalString, BMPString, ObjectDescriptor, GeneralizedTime, and UTCTime. These types inherit the same constructed-string reassembly path.

The DER decoder's OCTET STRING restriction replaces the tag-driven decoder, but its schema-driven TYPE_MAP retains the BER-derived decoder. Thus der.decode(data) rejects a constructed OCTET STRING, while der.decode(data, asn1Spec=univ.OctetString()) accepts its definite-length form and reaches the accumulation loop. DER does not replace the inherited character-string decoders, so a constructed UTF8String is also accepted without asn1Spec. These inputs violate X.690's DER string-encoding rule and should not be described as valid DER encodings.

The original short-fragment, definite-length OCTET STRING reproducer is valid BER. The CER decoder accepts it, but it is not a conforming CER string encoding: X.690 requires an indefinite-length outer constructed string and 1,000 contents octets per fragment except possibly the last. A conforming CER-shaped constructed OCTET STRING with those fragment sizes also reaches the loop and shows quadratic growth. The indefinite-length ANY reproducer below can represent a CER SEQUENCE OF OCTET STRING and reaches the separate ANY loop when decoded with an ANY schema.

The decoder's eight-octet length-field limit and nesting-depth limit do not bound the number of sibling fragments in these examples. The BER reproducer below uses only a three-octet outer length field; all fragments have shallow nesting. There is no per-message fragment-count cap in v0.6.4.

Impact

If an application passes attacker-controlled ASN.1 to the affected decode() or StreamingDecoder path without an adequate input or resource limit, a megabyte-scale value can occupy a decoding worker for seconds. Repeated inputs can exhaust available CPU. Whether this is reachable before authentication, or through a particular protocol, depends on the application; the library finding alone does not establish those conditions. No confidentiality or integrity impact is claimed.

Severity assessment (CVSS v3.1)

The 7.5 Base score assumes a reasonable application that automatically decodes attacker-supplied ASN.1 from unauthenticated network input. Valid BER and conforming CER inputs trigger the string-reassembly issue without special conditions, privileges, or user interaction. Repeated crafted inputs could occupy all available decoder workers and deny service while the attack continues. No confidentiality or integrity impact is demonstrated. This library-level score does not assert that every adopter has such an endpoint or loses all service; deployment-specific input limits and architecture may change the score. See FIRST's library-scoring guidance.

On the official v0.6.4 tag, local CPU-time measurements with CPython 3.14.8 on macOS arm64 gave the following results. Times vary by machine and Python runtime; the input-size scaling is the relevant observation. “Fixed” means the local 0.6.5 decoder fix applied to the same inputs.

Decoder path and input Fragments v0.6.4 CPU Fixed CPU
BER constructed OCTET STRING, 0.40 MB 100,000 × 2 payload bytes 0.340 s 0.160 s
BER constructed OCTET STRING, 0.80 MB 200,000 × 2 payload bytes 0.886 s 0.312 s
BER constructed OCTET STRING, 1.60 MB 400,000 × 2 payload bytes 2.738 s 0.623 s
BER indefinite ANY, 0.30 MB 100,000 × 1 payload byte 0.459 s 0.206 s
BER indefinite ANY, 0.60 MB 200,000 × 1 payload byte 1.245 s 0.412 s
BER indefinite ANY, 1.20 MB 400,000 × 1 payload byte 3.980 s 0.826 s
CER constructed OCTET STRING, 1.00 MB, conforming fragment layout 1,000 × 1,000 payload bytes 0.0089 s 0.0022 s
CER constructed OCTET STRING, 2.01 MB, conforming fragment layout 2,000 × 1,000 payload bytes 0.0311 s 0.0044 s
CER constructed OCTET STRING, 4.02 MB, conforming fragment layout 4,000 × 1,000 payload bytes 0.1379 s 0.0090 s

For the same short-fragment layout, the original report measured up to 16.05 seconds for a 3.2 MB BER constructed OCTET STRING. An independent report on CPython 3.11.16/Linux measured 5.76 seconds for a 2.4 MB constructed OCTET STRING, versus about 0.005 seconds for a primitive OCTET STRING of comparable size. These are reporter measurements on different systems, not fixed performance guarantees.

Reproduction

This example uses public decoder APIs. Run it with pyasn1 0.6.4 and increase n to observe the scaling. The final two calls show the DER paths accepted by v0.6.4; their constructed encodings are not valid DER.

import time

from pyasn1.codec.ber import decoder as ber
from pyasn1.codec.cer import decoder as cer
from pyasn1.codec.der import decoder as der
from pyasn1.type import univ


def tlv(identifier, contents):
    length = len(contents)
    if length < 128:
        return bytes((identifier, length)) + contents
    encoded = length.to_bytes((length.bit_length() + 7) // 8, "big")
    return bytes((identifier, 0x80 | len(encoded))) + encoded + contents


def measure(label, decode, substrate, asn1Spec=None):
    start = time.process_time()
    _, rest = decode(substrate, asn1Spec=asn1Spec)
    assert not rest
    print(f"{label}: {len(substrate)} input bytes, "
          f"{time.process_time() - start:.3f} CPU seconds")


n = 100_000
octet_fragments = b"\x04\x02AA" * n
utf8_fragments = b"\x0c\x02AA" * n

measure("BER constructed OCTET STRING", ber.decode,
        tlv(0x24, octet_fragments))
measure("BER indefinite ANY", ber.decode,
        b"\x30\x80" + b"\x04\x01A" * n + b"\x00\x00",
        univ.Any())

# CER uses indefinite length and 1,000-content-octet primitive fragments.
cer_fragment = tlv(0x04, b"A" * 1000)
measure("CER constructed OCTET STRING", cer.decode,
        b"\x24\x80" + cer_fragment * 1000 + b"\x00\x00")

# These constructed encodings are accepted by pyasn1 but are invalid DER.
measure("DER OCTET STRING with schema", der.decode,
        tlv(0x24, octet_fragments), univ.OctetString())
measure("DER UTF8String without schema", der.decode,
        tlv(0x2c, utf8_fragments))

Patch

pyasn1 0.6.5 replaces the growing bytes accumulator with a list of fragments and joins them once after decoding in all three affected loops. The OCTET STRING base-class change also covers the inheriting character-string decoders. This removes the repeated copying while preserving the assembled decoded value.

Workaround

Until upgrading, enforce application-appropriate limits on the size of untrusted ASN.1 input before passing it to pyasn1, and limit the CPU resources available to decoding workers. Applications that require DER can also reject constructed string encodings before invoking the decoder. The appropriate byte limit depends on the protocol and expected valid values.

Credits and references

Reported by alimony. Independently reported by manus-pi, whose report identified the schema-driven DER route, inherited character-string decoders, and additional performance evidence.

References

@droideck droideck published to pyasn1/pyasn1 Oct 27, 2026
Published by the National Vulnerability Database Oct 29, 2026
Published to the GitHub Advisory Database Oct 30, 2026
Reviewed Oct 30, 2026
Last updated Oct 30, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(45th percentile)

Weaknesses

Inefficient Algorithmic Complexity

An algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached. Learn more on MITRE.

CVE ID

CVE-2026-119195

GHSA ID

GHSA-9m75-vc3g-4x39

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.