DEV Community

CVE Reports
CVE Reports

Posted on Originally published at cvereports.com

CVE-2026-119196: CVE-2026-119196: Quadratic CPU Consumption in pyasn1 Constructed BIT STRING Decoding

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Audit installed Python packages and lockfiles (requirements.txt, Pipfile.lock, poetry.lock) using pip-audit or pip list to identify pyasn1 versions prior to 0.6.5.
  2. Execute pip install --upgrade "pyasn1>=0.6.5" in affected environments and rebuild deployment artifacts.
  3. Verify the installed version by running python -c "import pyasn1; print(pyasn1.__version__)" and confirming the output is 0.6.5 or higher.
  4. Restart long-running Python services and worker daemons to ensure the updated pyasn1 module is loaded into memory.

References


Read the full report for CVE-2026-119196 on our website for more details including interactive diagrams and full exploit analysis.

Top comments (0)