A critical denial-of-service vulnerability in the go-ipld-prime library lets attackers crash Go applications by forcing massive memory allocations from tiny CBOR payloads. The DAG-CBOR decoder blindly trusts collection size hints in CBOR headers, preallocating huge Go slices for maps and lists without checking against its own allocation budget. A payload under 100 bytes with 10 levels of nesting can trigger over 9GB of allocation, exhausting server memory in seconds.
This hits hard in decentralized ecosystems like IPFS and Filecoin, where nodes routinely parse untrusted IPLD blocks. IPLD-prime, the Go reference implementation for InterPlanetary Linked Data, powers these systems by decoding content-addressed data serialized in CBOR—a compact binary format for IPLD DAGs. Without bounds, remote peers send malformed blocks, your node OOMs, and service halts. No code execution, just effective DoS.
Vulnerability Mechanics
CBOR headers declare map or list lengths upfront. Go’s decoder in go-ipld-prime uses these as hints to preallocate backing slices via make([]T, n), where n comes straight from the header. No cap exists, and the library’s allocation budget—meant to limit total decode costs—ignores this preallocation. It only deducts as actual entries decode.
Nesting amplifies the issue. Each nested collection declares its own oversized length, consuming just one budget unit per level from the parent. Schema-free decoding with basicnode.Prototype.Any allows unlimited depth. Even schema-bound decoding fails if any field types as Any, opening a nesting vector.
Consider this attack payload sketch in CBOR hex (under 100 bytes):
# Hypothetical nested maps, each declaring 2^30 entries
# Level 1: map of 1<<30 entries, but only 1 child
# Child repeats 10 levels deep
# Actual decode reads 10 entries total, but preallocs explode
Go preallocates 1 billion+ entries per level. Ten levels? Cascade to gigabytes. Tests confirm: such inputs allocate 9GB+ before choking. Real-world vectors abound—IPFS fetches, Filecoin deals, any IPLD ingress.
The Fix and Affected Versions
Upstream patched in v0.21.0 (released October 2023). They cap preallocation at 1024 entries per collection. Budget now decrements on size declaration, proportional to declared length. Validation still uses the full declared size, and slices grow dynamically if needed. Legit data over 1024 entries works fine; attackers hit budget walls early.
Check your deps: go-ipld-prime < v0.21.0 vulnerable. IPFS Go kubo v0.20+ integrates the fix, but verify. Filecoin nodes lag sometimes—scan go list -m all | grep ipld and update. No CVEs assigned yet; track GitHub advisory #129.
Why This Matters and Broader Lessons
Decentralized storage thrives on trust-minimized parsing, but this exposes how header trust bites. Filecoin's 15+ EiB of data (as of Q3 2024) means millions of nodes at risk. A coordinated spray of bad blocks could disrupt gateways, deals, retrievals. We've seen similar in other decoders—JSON bombs, protobuf unbounded fields. Skeptical eye: libraries assume benign inputs, but blockchains don't deliver.
Mitigate now: Pin budgets low (e.g., 1MB), sandbox decoders, rate-limit peers. Audit schemas—ban Any where possible. For prod, wrap decoders:
func SafeDecode(data []byte, budget int64) error {
return cbor.Decoder(data, node.Prototype.Any, node.NewBuilder(),
cbor.DecoderLimits{AllocationBudget: budget})
}
Call with budget: 1 << 20 (1MB). Test exhaustively—fuzz with go-fuzz or similar. This isn't hype; it's a textbook memory DoS in a cornerstone lib. Update, bound, watch traffic. Your uptime depends on it.
IPLD's growth—powering Ethereum's Verkle trees, Ceramic streams—amplifies stakes. Fair play: the maintainers acted fast post-disclosure. But it underscores: in crypto infra, preallocation sans sanity is a loaded gun. Patch, then harden.