Walrus storage-node HTTP/2 Rapid Reset: memory exhaustion via u32::MAX default
The Walrus storage-node service ships an HTTP/2 server whose http2_max_pending_accept_reset_streams is defaulted to u32::MAX, a value plumbed straight into the axum-server 0.8 HTTP/2 builder and reinforced in the published configuration example. That setting disables the post-CVE-2023-44487 accounting limit that the Rust h2 crate added specifically to bound Rapid Reset accumulation. An unauthenticated attacker opens HTTP/2 streams and immediately cancels each one (HEADERS → RST_STREAM) in a rapid cycle — the classic HTTP/2 Rapid Reset (CVE-2023-44487 class) — so pending-accept-reset stream state accumulates without bound, pinning memory until OOM, and the server never issues a GOAWAY to shed load. This is a resource-bounding misconfiguration / hardening class, not a novel implementation flaw.
Why this matters
The exposed party is any Walrus storage node running the default configuration. The HTTP/2 REST endpoint is public-by-default — no auth, no allowlist, no token required to reach it. The only gate between an attacker and the unbounded memory pin is the operator's choice to override the compiled default (config.rs:1800) from walrus-node.yaml.
Two properties make this more than a configuration warning. First, the disabled limit is the same accounting knob that the Rust h2 crate added as the specific mitigation for CVE-2023-44487 — the setting exists precisely to bound Rapid Reset accumulation, and Walrus's default opts back out of it by setting it to u32::MAX (4,294,967,295). Second, the measurement is production-faithful and severe: on a mirror with the same axum-server 0.8 + h2 0.4 + rustls 0.23 crate set as upstream walrus-service, a single attacker host with 4 concurrent TLS connections pins ~28 GiB RSS (~4,933× growth) and ~12.4 cores in ~60 seconds, at sub-volumetric traffic (~40 MiB/s out). The server issues zero GOAWAY frames to protect itself; memory is not reclaimed after disconnect → OOM under sustained attack.
The impact is availability-only: the storage-node HTTP/2 REST surface is denied via OOM. There is no consensus-safety break, no funds at risk, no authentication bypass, and no data corruption. A node configured with a bounded http2_max_pending_accept_reset_streams (e.g. 200, or the h2 library default of 20) is not affected.
How the attack works
- Entry point: the storage-node's HTTP/2 REST port, which is public-by-default in the upstream
walrus-nodeconfiguration. No auth required. - Preconditions: the attacker can reach the HTTP/2 port from any IP. No credentials, no session, no application-layer protocol compliance beyond HTTP/2. Remote, unauthenticated.
- Processing path:
- The disabled limit.
crates/walrus-service/src/node/config.rs:1800defaultshttp2_max_pending_accept_reset_streams = u32::MAX. Theh2crate addedmax_pending_accept_reset_streams(default 20) specifically to bound the Rapid-Reset accumulation post CVE-2023-44487; setting it tou32::MAXopts back out. - Plumbed into the server. The value flows into the
axum-server 0.8http_builder().http2()chain atserver.rs, so the live server advertises/uses the unbounded limit (runtime dump printshttp2_max_pending_accept_reset_streams: 4294967295). - The attack pattern. The client opens a stream (
HEADERS) and immediately sendsRST_STREAMbefore the server finishes accepting it. Repeated rapidly, the "pending accept reset" queue grows unbounded. Each cancelled stream pins memory representing the not-yet-accepted state. - No GOAWAY, no reclaim. The server emits no
GOAWAYframe to protect itself, and memory is not reclaimed after attacker disconnect → OOM under sustained attack.
- The disabled limit.
- Boundary crossed: the pre-CVE-2023-44487
h2accounting limit, which exists precisely to prevent this class, is disabled at compile-time by default. Unbounded pending-accept-reset state accumulates on the server's heap. - Resulting behaviour: memory pin → OOM on the HTTP/2 REST surface. The attack is sub-volumetric (~40 MiB/s attacker traffic for ~28 GiB RSS victim growth), so network-layer volumetric gating does not help.
The mechanism is a resource-bounding misconfiguration / hardening class — the code path is the post-CVE-2023-44487 h2 accounting, and the finding is that Walrus's default disables it. This is not a novel implementation flaw; it is a CVE-2023-44487-class issue.
Affected systems
- Severity: High (per NR-2026-038) — measured OOM on production-faithful mirror: 4 connections, 60 seconds → ~28 GiB RSS (~4,933×), ~12.4 cores, sub-volumetric traffic (~40 MiB/s), zero
GOAWAY. - Chain: Walrus.
- Implementation:
walrus-node(boot-verified on1.48.1; every storage node running the default config carries the sameu32::MAXvalue). - Component: storage-node HTTP/2 REST API (
axum-server 0.8/h2crate), configured viahttp2_max_pending_accept_reset_streams. - Gated on: the operator's configuration. A node configured with a bounded
http2_max_pending_accept_reset_streams(e.g.200, or theh2default of20) is not affected. The default is the vulnerability; the fix is a one-linewalrus-node.yamlchange effective on next restart, no binary upgrade required.
Evidence
- Publicly documented: CVE-2023-44487 is the class (HTTP/2 Rapid Reset), but this is not a Walrus-assigned CVE. The finding is our own measurement of the walrus-specific
u32::MAXdefault — a CVE-2023-44487-class misconfiguration, not a novel implementation flaw. The advisory explicitly states: "not an assigned Walrus CVE." - Measured on production-faithful mirror: a single attacker host with 4 concurrent TLS connections, 60 seconds sustained on a mirror with the same
axum-server 0.8+h2 0.4+rustls 0.23crate set:- Victim RSS: 5,960 KiB → ~28 GiB pinned (~4,933× growth)
- Victim CPU: ~1,245% (~12.4 cores)
- Attacker traffic: ~2.34 GiB out (~40 MiB/s — well below any volumetric threshold)
- Server
GOAWAYframes issued: zero; memory not reclaimed after disconnect → OOM under sustained attack
- Safe-mode baseline: the same mirror, limit left at the
h2library default of 20, tears the attacker down inside ~5 ms with the expectedh2warning log — no memory growth. - Independently reproduced by NullRabbit (lab): the published corpus reproducer (
walrus_http2_rapid_reset,source_class=original, shipped inNullRabbit/nr-bundles-public) captures the attack traffic — rapidHEADERS→RST_STREAMcycles across postures, with zeroGOAWAY. - Not established: no live/mainnet Walrus node was crashed. The measurement is on a production-faithful mirror, not a live production node.
Operator actions
Available today; no binary upgrade required (per NR-2026-038):
- Set a bounded limit —
http2_max_pending_accept_reset_streams: 200(or theh2default of20) inwalrus-node.yaml— effective on next restart. This is the one-line fix. - Change the compiled default — modify
crates/walrus-service/src/node/config.rs:1800andnode_config_example.yamlso new deployments inherit a safe value. - Front the HTTP/2 REST port with a rapid-reset-aware, GOAWAY-emitting gateway where a bounded upstream cannot be relied upon.
Canonical references
- NRDAX technique: NRDAX-T0112 — http2-rapid-reset (family
memory_amp). - NullRabbit advisory: NR-2026-038 — Walrus HTTP/2 Rapid Reset (published 2026-07-13).
- Evidence bundle (Hugging Face): NullRabbit/nr-bundles-public — primitive
walrus_http2_rapid_reset(4 bundles shipped). - Upstream primary sources:
MystenLabs/walrusconfig.rs:1800(http2_max_pending_accept_reset_streamsdefault),server.rs:300(axum-server HTTP/2 builder). - CVE class: CVE-2023-44487 — HTTP/2 Rapid Reset (the class; this finding is walrus-specific
u32::MAXdefault, not the CVE itself).
Track attacks affecting Walrus on NRDAX and in NullRabbit research.
Production-faithful mirror measurement + reproducer. No live mainnet crash demonstrated.
Related Posts
rusty-kaspa gRPC pre-auth HTTP/2 stream flood causing inbound-peering exhaustion and eviction
A pre-auth HTTP/2 stream flood on the rusty-kaspa gRPC P2P endpoint causes inbound-peering exhaustion via eviction and admission denial.
One prior-knowledge h2c connection multiplexes N eth_getLogs past an L4 per-connection cap on go-ethereum's JSON-RPC port
go-ethereum's JSON-RPC port terminates cleartext HTTP/2 (h2c) by prior knowledge, so one TCP connection can multiplex N eth_getLogs streams past an L4 edge that only caps connections per IP (nginx stream limit_conn) — measured 20/20 streams and ~39 MB pulled through a single connection the edge counts as one, an amplification-multiplexing bypass of a connection cap, not of an L7 request rate limit.
Bypassing go-ethereum's --http.vhosts host allowlist with an empty Host, forged by an HAProxy HTTP/2→1.1 downgrade
An HTTP/2 request with an empty :authority, downgraded to HTTP/1.1 by an HAProxy front, reaches go-ethereum with an empty Host header that its --http.vhosts allowlist accepts (200) though it rejects any concrete disallowed host (403) — a bypass of geth's anti-DNS-rebinding Host filter by an attacker with raw-HTTP/2 reach to the edge.
