CVE-2026-119196: Quadratic CPU Consumption in pyasn1 Constructed BIT STRING Decoding
Vulnerability ID: CVE-2026-119196
CVSS Score: 7.5
Published: 2026-10-30
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.
TL;DR
pyasn1 versions prior to 0.6.5 exhibit quadratic time complexity when reassembling constructed ASN.1 BIT STRING fragments. Remote attackers can transmit payloads containing thousands of small fragments to exhaust CPU resources and block application worker threads. Upgrading to pyasn1 0.6.5 resolves the issue by accumulating fragments into a mutable bytearray prior to instantiation.
⚠️ Exploit Status: POC
Technical Details
- Vulnerability Type: CWE-407: Inefficient Algorithmic Complexity
- Attack Vector: Network (Unauthenticated ASN.1 BER/CER/DER Payload)
- CVSS v3.1 Score: 7.5 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
- EPSS Score: 0.43% (36.80th Percentile)
- Primary Impact: Denial of Service via Quadratic CPU Exhaustion
- Affected Component: pyasn1 BitStringPayloadDecoder (< 0.6.5)
- Patched Version: pyasn1 0.6.5
- CISA KEV Status: Not Listed
Affected Systems
- pyasn1
-
pyasn1: < 0.6.5 (Fixed in:
0.6.5)
Code Analysis
Commit: 73ba7b0
Remove quadratic constructed BIT STRING reassembly
diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py
index 05525c6a..d0b9ee19 100644
--- a/pyasn1/codec/ber/decoder.py
+++ b/pyasn1/codec/ber/decoder.py
@@ -174,6 +174,44 @@ class BitStringPayloadDecoder(AbstractSimplePayloadDecoder):
protoComponent = univ.BitString(())
supportConstructedForm = True
+ @staticmethod
+ def _appendFragment(assembled, component, bitLength):
+ trailingBits = component[0]
+ if trailingBits > 7:
+ raise error.PyAsn1Error(
+ 'Trailing bits overflow %s' % trailingBits
+ )
+
+ payload = component[1:]
+ if not payload:
+ if trailingBits:
+ raise error.PyAsn1Error('Trailing bits overflow %s' % trailingBits)
+ return bitLength
+
+ if not trailingBits and not bitLength % 8:
+ assembled.extend(payload)
+ return bitLength + len(payload) * 8
+
+ for idx, byte in enumerate(payload):
+ significantBits = 8
+ if idx == len(payload) - 1:
+ significantBits -= trailingBits
+ byte >>= trailingBits
+
+ usedBits = bitLength % 8
+ if not usedBits:
+ assembled.append(byte << (8 - significantBits))
+ elif usedBits + significantBits <= 8:
+ assembled[-1] |= byte << (8 - usedBits - significantBits)
+ else:
+ remainingBits = usedBits + significantBits - 8
+ assembled[-1] |= byte >> remainingBits
+ assembled.append((byte << (8 - remainingBits)) & 0xff)
+
+ bitLength += significantBits
+
+ return bitLength
+
def valueDecoder(self, substrate, asn1Spec,
tagSet=None, length=None, state=None,
decodeFun=None, substrateFun=None,
@@ -230,7 +268,8 @@ def valueDecoder(self, substrate, asn1Spec,
# All inner fragments are of the same type, treat them as octet string
substrateFun = self.substrateCollector
- bitString = self.protoComponent.fromOctetString(b'', internalFormat=True)
+ assembled = bytearray()
+ bitLength = 0
current_position = substrate.tell()
@@ -241,16 +280,11 @@ def valueDecoder(self, substrate, asn1Spec,
if isinstance(component, SubstrateUnderrunError):
yield component
- trailingBits = component[0]
- if trailingBits > 7:
- raise error.PyAsn1Error(
- 'Trailing bits overflow %s' % trailingBits
- )
+ bitLength = self._appendFragment(assembled, component, bitLength)
- bitString = self.protoComponent.fromOctetString(
- component[1:], internalFormat=True,
- prepend=bitString, padding=trailingBits
- )
+ bitString = self.protoComponent.fromOctetString(
+ assembled, internalFormat=True, padding=(-bitLength) % 8
+ )
yield self._createComponent(asn1Spec, tagSet, bitString, **options)
@@ -270,7 +304,8 @@ def indefLenValueDecoder(self, substrate, asn1Spec,
# All inner fragments are of the same type, treat them as octet string
substrateFun = self.substrateCollector
- bitString = self.protoComponent.fromOctetString(b'', internalFormat=True)
+ assembled = bytearray()
+ bitLength = 0
while True: # loop over fragments
@@ -287,16 +322,11 @@ def indefLenValueDecoder(self, substrate, asn1Spec,
if component is eoo.endOfOctets:
break
- trailingBits = component[0]
- if trailingBits > 7:
- raise error.PyAsn1Error(
- 'Trailing bits overflow %s' % trailingBits
- )
+ bitLength = self._appendFragment(assembled, component, bitLength)
- bitString = self.protoComponent.fromOctetString(
- component[1:], internalFormat=True,
- prepend=bitString, padding=trailingBits
- )
+ bitString = self.protoComponent.fromOctetString(
+ assembled, internalFormat=True, padding=(-bitLength) % 8
+ )
yield self._createComponent(asn1Spec, tagSet, bitString, **options)
Exploit Details
- GitHub: Official patch commit containing regression unit tests demonstrating constructed BIT STRING fragment decoding.
Mitigation Strategies
- Upgrade pyasn1 to version 0.6.5 or later across all virtual environments, container images, and transitive dependency lockfiles.
- Enforce strict transport-layer and application-layer maximum payload size limits before passing untrusted byte streams to pyasn1 decoders.
- Disable constructed BIT STRING decoding (supportConstructedForm = False) in custom decoder subclasses if protocol specifications strictly require primitive DER encoding.
- Configure process-level execution timeouts on worker processes handling untrusted ASN.1 inputs to terminate threads stalled by CPU exhaustion.
Remediation Steps:
- Audit installed Python packages and lockfiles (
requirements.txt,Pipfile.lock,poetry.lock) usingpip-auditorpip listto identifypyasn1versions prior to0.6.5. - Execute
pip install --upgrade "pyasn1>=0.6.5"in affected environments and rebuild deployment artifacts. - Verify the installed version by running
python -c "import pyasn1; print(pyasn1.__version__)"and confirming the output is0.6.5or higher. - Restart long-running Python services and worker daemons to ensure the updated
pyasn1module is loaded into memory.
References
- GitHub Security Advisory: GHSA-2qm8-x737-gmw5
- Official Patch Commit (73ba7b043cc7e92cb37f16f2c955bff93b0e6f6c)
- pyasn1 Pull Request #132
- pyasn1 v0.6.5 Release
- NIST National Vulnerability Database (NVD) - CVE-2026-119196
- CVE.org Record: CVE-2026-119196
- Red Hat Bugzilla #2563666
- Red Hat Security CVE Tracker: CVE-2026-119196
Read the full report for CVE-2026-119196 on our website for more details including interactive diagrams and full exploit analysis.
Top comments (0)