Skip to content

GHSA-2qm8-x737-gmw5 CVE-2026-119196

pyasn1 has quadratic CPU consumption in constructed BIT STRING decoding

High PyPI
Severity
7.5 / 10
High · CVSS 3.1
Exploit chance (EPSS)
0.43%
Chance of exploitation in the next 30 days. Higher than 36.82% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-407 Inefficient Algorithmic Complexity
Fix
0.6.5
Upgrade pyasn1 to this version or later

Description

Summary

In pyasn1 through 0.6.4, decoding a constructed BIT STRING with many fragments repeatedly shifts and copies the entire value accumulated so far. For fixed-size fragments, CPU time grows quadratically with the encoded input size. An application that passes sufficiently large, attacker-controlled ASN.1 data to an affected decoder can suffer CPU exhaustion during decode(); no conversion or formatting of the decoded value is required.

The BER and CER decoders reach this path for constructed BIT STRING values, with or without an asn1Spec. The DER decoder rejects constructed BIT STRING in schema-less decoding, but its schema-driven path accepts a definite-length constructed BIT STRING when given a matching asn1Spec and reaches the same vulnerable code. Constructed BIT STRING is not valid DER; this DER exposure is due to decoder permissiveness. DER rejects the indefinite-length form.

This is a separate decoder path from the OBJECT IDENTIFIER accumulation fixed in CVE-2026-59885.

Technical details

In the 0.6.4 BitStringPayloadDecoder, both the definite-length and indefinite-length fragment loops call BitString.fromOctetString(..., prepend=bitString) for every fragment. fromOctetString shifts the growing SizedInteger left by the fragment's bit length and ORs in the fragment. Shifting an ever-larger Python integer for each fixed-size fragment makes total work O(n²) for n fragments. The fragments are siblings, so the decoder's nesting-depth limit does not constrain their count; its limit on the number of length octets does not prevent the demonstrated inputs.

In 0.6.4, DER's tag-driven map disables constructed BIT STRING, while its schema-driven type map retains the inherited decoder that permits it. This explains why a schema-less DER rejection is not a negative result for all DER decoding.

Under X.690, a conforming CER constructed BIT STRING uses an indefinite-length outer encoding and primitive fragments with 1000 contents octets except possibly the last. That form reaches the affected indefinite-length loop. DER requires primitive string encoding, although the schema-driven pyasn1 decoder accepts the definite-length constructed example below.

Impact

An application is exposed when untrusted input can reach one of the affected decoder paths and its input limits admit enough fragments. The work occupies the decoding thread or worker and may cause denial of service. Whether the input is network-accessible or unauthenticated depends on the calling application. This advisory makes no claim that every application using pyasn1, or any particular protocol implementation, accepts such input.

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 values trigger the issue without special conditions, privileges, or user interaction. Repeated crafted values could occupy a finite decoder-worker pool and deny service while the attack continues. No confidentiality or integrity impact is demonstrated. This library-level score does not claim that every application is network-exposed or fully unavailable; deployment-specific input limits and architecture may change the score. See FIRST's library-scoring guidance.

Reproducer

The definite-length example below is valid BER. It is deliberately not a conforming CER or DER encoding. The second constructor uses conforming CER fragment sizing and framing. CPU times are machine-dependent; run the larger cases only in a local test environment.

import time

from pyasn1.codec.ber import decoder as ber_decoder
from pyasn1.codec.cer import decoder as cer_decoder
from pyasn1.codec.der import decoder as der_decoder
from pyasn1.error import PyAsn1Error
from pyasn1.type import univ


def encode_length(size):
    octets = size.to_bytes((size.bit_length() + 7) // 8, "big")
    return bytes((0x80 | len(octets),)) + octets


def definite_fragments(count):
    body = b"\x03\x02\x00\xa5" * count  # one data octet per BIT STRING fragment
    return b"\x23" + encode_length(len(body)) + body


def cer_fragments(count):
    # Each primitive fragment has 1000 contents octets: one unused-bits
    # octet (zero) followed by 999 data octets.
    fragment = b"\x03" + encode_length(1000) + b"\x00" + b"\xa5" * 999
    return b"\x23\x80" + fragment * count + b"\x00\x00"


def measure(label, decode, payload, **options):
    start = time.process_time()
    value, remainder = decode(payload, **options)
    print(label, len(payload), f"{time.process_time() - start:.3f}s CPU")
    assert not remainder and len(value) > 0


for count in (50_000, 100_000, 200_000):
    measure(f"BER {count} fragments", ber_decoder.decode,
            definite_fragments(count))

for count in (1_000, 2_000, 4_000):
    measure(f"CER {count} fragments", cer_decoder.decode,
            cer_fragments(count))

sample = definite_fragments(1_000)
measure("DER with BIT STRING schema", der_decoder.decode, sample,
        asn1Spec=univ.BitString())
try:
    der_decoder.decode(sample)
except PyAsn1Error:
    print("DER without schema rejects the constructed value")
else:
    raise AssertionError("DER unexpectedly accepted schema-less input")

Verification

Local measurements of the official v0.6.4 tag on CPython 3.14.8 used time.process_time(). A local 0.6.5 candidate containing the fix was timed on the same machine. Times are illustrative, not a guaranteed cost on other systems.

Input Size v0.6.4 CPU 0.6.5 candidate CPU
BER definite, 50,000 fragments 0.20 MB 0.530 s 0.084 s
BER definite, 100,000 fragments 0.40 MB 1.915 s 0.170 s
BER definite, 200,000 fragments 0.80 MB 7.392 s 0.344 s
Conforming CER, 1,000 fragments 1.004 MB 0.163 s 0.0035 s
Conforming CER, 2,000 fragments 2.008 MB 0.657 s 0.0070 s
Conforming CER, 4,000 fragments 4.016 MB 2.582 s 0.0144 s

The definite BER input with 100,000 fragments also decoded through DER with asn1Spec=univ.BitString() in 1.949 s on v0.6.4, versus 0.172 s with the 0.6.5 candidate. Schema-less DER rejected the same input. Independent reports reproduced the old behavior on v0.6.4: one measured 21.1 s for a 1.28 MB definite-length value; another measured 7.25 s for a 0.8 MB value versus 2.8 ms for a primitive BIT STRING carrying the same data volume. These measurements were taken on different systems and should not be compared as a single benchmark series.

Resolution

Upgrade to pyasn1 0.6.5. The decoder assembles fragment bits in a bytearray, preserves decoded bit values including intermediate-fragment padding, and converts the complete payload to a BIT STRING once. This removes the repeated shift of the growing integer in both constructed decoding loops.

For applications that cannot upgrade immediately, enforce an application-appropriate limit on untrusted ASN.1 input size before passing it to pyasn1. Such a limit bounds exposure but does not change the quadratic behavior of 0.6.4. Where a protocol forbids constructed BIT STRING, rejecting that encoding before decoding also prevents this path.

Affected versions

PyPI

PackageVulnerableFixed in
pyasn1
<= 0.6.4
No fix yet
pyasn1
< 0.6.5
0.6.5

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

pyasn1 PyPI → 0.6.5

pip
pip install --upgrade "pyasn1>=0.6.5"
poetry
poetry add "pyasn1>=0.6.5"
uv
uv add "pyasn1>=0.6.5"

Severity breakdown

CVSS 3.1 7.5

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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

Weakness

CWE-407: 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.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-mm57-4v3p-rchx
pyasn1 has quadratic-complexity denial of service in binary REAL decoding
High Also affects pyasn1
GHSA-9m75-vc3g-4x39
pyasn1 has quadratic-time denial of service in constructed string and indefinite-length ANY decoding
High Also affects pyasn1
GHSA-fm9c-5rcf-2q5p
pyasn1 has quadratic CPU use in schemaless BER/CER/DER container decoding
High Also affects pyasn1
GHSA-hm4w-wwcw-mr6r
pyasn1: Uncontrolled resource consumption when converting decoded REAL values
High Also affects pyasn1
GHSA-8ppf-4f7h-5ppj
pyasn1: Quadratic complexity in OBJECT IDENTIFIER and RELATIVE-OID processing allows denial of service
High Also affects pyasn1
GHSA-m4p7-r5rc-7g4j
pyasn1 BER/CER/DER decoder denial of service via unbounded long-form tag IDs
High Also affects pyasn1
GHSA-jr27-m4p2-rc6r
Denial of Service in pyasn1 via Unbounded Recursion
High Also affects pyasn1
GHSA-63vm-454h-vhhq
pyasn1 has a DoS vulnerability in decoder
High Also affects pyasn1

Credits: adriank31, manus-pi, manus-use, ucsurf

Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor