Skip to content

ASN.1 DER decoder drives an unbounded synchronous loop on attacker-controlled length fields, stalling the Node event loop pre-auth

High
tabcat published GHSA-rvfr-cqwq-rc4j Aug 18, 2026

Package

npm @libp2p/crypto (npm)

Affected versions

<= 5.1.21

Patched versions

None

Description

Summary

A remote peer can stall a js-libp2p node's entire event loop for ~4–5 seconds per connection (and transiently allocate ~2 GB) by sending a ~15-byte crafted publicKey inside an Identify message. The malformed key reaches the DER decoder before any peer-id/signature check, so the attack is pre-auth and repeatable indefinitely by reconnecting.

Details

packages/crypto/src/keys/rsa/der.ts readLength() parses DER long-form lengths with no comparison to buf.byteLength, and readInteger() then loops over the attacker-claimed length:

function readInteger (buf, context) {
  const length = readLength(buf, context)   // attacker-controlled, claims up to 2^64
  const end = context.offset + length
  for (let i = context.offset; i < end; i++) // runs far past buf.byteLength
    vals.push(buf[i])                         // pushes `undefined` past the end
}

The blob reaches the parser pre-auth: Identify runs on every connection:open and calls publicKeyFromProtobuf(publicKey) (packages/protocol-identify/src/identify.ts:85) before the connection.remotePeer.equals(id) check at identify.ts:88. The MAX_RSA_JWK_SIZE guard in pkixToRSAPublicKey only bounds the total blob size, not the inner length field, so a 15-byte payload defeats it; the ECDSA path (unmarshalECDSAPublicKey) has no cap at all. The decode runs in a single synchronous tick, so no AbortSignal — including Identify's 5 s timeout — can interrupt it. Verified on js-libp2p commit a04f5e0, where @libp2p/crypto reads 5.1.21.

PoC

Full PoC attached (libp2p-crypto-der-eventloop-dos-poc.tar.gz). Build the js-libp2p monorepo at the affected version (npm install --ignore-scripts && npm run build), extract the package inside the monorepo root (sibling of packages/), then cd libp2p-crypto-der-eventloop-dos-poc && node attack.mjs. Expected:

[*] round 1: payload sent=true, victim event-loop stall = 4s, peak RSS ~1980 MB
[*] round 2: payload sent=true, victim event-loop stall = 4-5s, peak RSS ~1980 MB
[*] round 3: payload sent=true, victim event-loop stall = 4s, peak RSS ~1980 MB
[!] Vulnerability reproduced: 3/3 connections each stalled the victim's event loop for ~4-5s — one crafted ~15-byte Identify publicKey per connection, pre-auth

The stall is measured from the victim's own 1 s heartbeat clock (frozen while the loop is blocked); the ~2 GB transient RSS peak is read from /proc/<pid>/status. The DER RangeError is swallowed by Identify's .catch(() => {}), so the node survives and every new connection re-stalls it; under a container memory limit the ~2 GB spike OOM-kills the process. A node attack.mjs control run (legitimate Identify) shows 0 s stalls.

Impact

CWE-130/CWE-400. Any node running the default Identify service is affected. A single peer can keep the node unresponsive to all of its peers (every new connection adds another multi-second stall — all timers, heartbeats, and protocol timeouts stop) and, in memory-constrained deployments, force an OOM kill. Suggested fix: in readLength, reject any long-form length whose value exceeds buf.byteLength - context.offset; in readInteger/readObjectIdentifier/readBitString/readOctetString, clamp or assert end/finalOffset against buf.byteLength before looping; add an overall DER-element-length sanity cap at decodeDer entry and a byte-length cap in unmarshalECDSAPublicKey.

libp2p-crypto-der-eventloop-dos-poc.tar.gz

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

CVE ID

No known CVE

Weaknesses

Improper Handling of Length Parameter Inconsistency

The product parses a formatted message or structure, but it does not handle or incorrectly handles a length field that is inconsistent with the actual length of the associated data. Learn more on MITRE.

Uncontrolled Resource Consumption

The product does not properly control the allocation and maintenance of a limited resource. Learn more on MITRE.

Credits