highCVE-2026-84304Sep 1, 2026

CVE-2026-84304: gRPC-Go Heap Memory Exhaustion via HTTP/2 DATA Frame Fragmentation

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Any unauthenticated attacker can crash a gRPC-Go server by sending a large number of tiny HTTP/2 DATA frames across multiple streams, consuming far more heap memory than the actual payload size would…

Packagegoogle.golang.org/grpc
Ecosystemgo
Affected<= 1.83.0
Fixed in1.83.1
CVE-2026-84304: gRPC-Go Heap Memory Exhaustion via HTTP/2 DATA Frame Fragmentation

The problem

In gRPC-Go <= 1.83.0, internal/transport/transport.go stores every incoming HTTP/2 DATA frame as its own recvMsg entry in recvBuffer, regardless of frame size. Each entry carries fixed-size metadata and allocation overhead on top of the actual byte payload.

An attacker who fragments a stream payload into millions of 1-byte DATA frames forces the server to allocate millions of those tracking structures. Multiplexing several such streams concurrently can exhaust the Go runtime heap, triggering an out-of-memory panic and a remote denial of service.

No authentication is required.

Proof of concept

A working proof-of-concept for CVE-2026-84304 in google.golang.org/grpc, with the exact payload below.

text
# Open N concurrent gRPC streams to the target server.
# On each stream, send the request body as millions of individual
# 1-byte HTTP/2 DATA frames instead of one normal-sized frame.
# The total byte volume stays within flow-control limits (e.g. 65535 bytes),
# so the server never applies backpressure -- yet each frame costs a
# full recvMsg allocation on the heap.
#
# Conceptual HTTP/2 frame sequence per stream (repeated ~65535 times):
#
#   DATA frame (stream_id=N, length=1, flags=0x00)  <1 byte payload>
#   DATA frame (stream_id=N, length=1, flags=0x00)  <1 byte payload>
#   ... (repeat until heap exhausted)
#   DATA frame (stream_id=N, length=1, flags=0x01)  END_STREAM
#
# Raw HTTP/2 binary layout of a single 1-byte DATA frame:
# +--------+--------+--------+--------+--------+--------+--------+--------+--------+
# |       Length (3 bytes) = 0x000001  |  Type=DATA(0x00) | Flags=0x00 |
# +--------+--------+--------+--------+--------+--------+--------+--------+--------+
# |                    Stream Identifier (31 bits, e.g. 0x00000001)                |
# +--------+--------+--------+--------+--------+--------+--------+--------+--------+
# |  Data (1 byte)  |
# +------------------+
#
# Hex bytes for one such frame:
00 00 01   # Length = 1
00         # Type   = DATA
00         # Flags  = none
00 00 00 01  # Stream ID = 1
AA         # 1-byte payload
#
# Repeat this frame millions of times across multiple concurrent streams.

The root cause is that recvBuffer in internal/transport/transport.go allocates a new recvMsg struct for every DATA frame received, no matter how small. HTTP/2 flow control only caps the total bytes in flight, not the number of frames, so an attacker can legally send millions of frames before the window closes.

The patch (PR #9331, commit 7354d9c; backported in PR #9333, commit 8cfeca0) adds receive-buffer compaction: once the ratio of per-frame overhead to actual payload data becomes excessive, consecutive small buffers are coalesced into a single larger buffer drawn from a shared pool.

This collapses millions of 1-byte recvMsg entries into far fewer pooled allocations, eliminating the amplification. The feature is on by default and can be temporarily disabled with GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION=false.

CWE-400 (Uncontrolled Resource Consumption): the server had no bound on the number of frame-level allocations independent of flow-control byte limits.

The fix

Upgrade to google.golang.org/grpc v1.83.1 or later. The fix is included in commit 7354d9c8 (master) and commit 8cfeca0e (v1.83.x backport). No code changes are required after upgrading; receive-buffer compaction is enabled by default. If the new behavior causes regressions, set the environment variable GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION=false as a temporary escape hatch (this variable will be removed in a future release).

Reporter not attributed.

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

Related research