Like mailing someone a million envelopes each containing a single letter — the paper costs nothing but the mailroom runs out of shelf space and shuts down
CVE-2026-84304 is a denial-of-service vulnerability in google.golang.org/grpc versions ≤ 1.83.0. The flaw lives in internal/transport/transport.go, where each incoming HTTP/2 DATA frame is stored as a separate recvMsg in a recvBuffer. An unauthenticated remote attacker opens a gRPC stream and fragments their payload into millions of 1-byte HTTP/2 DATA frames. Because the total payload stays within HTTP/2 flow-control windows, the server happily accepts the frames — but each tiny frame carries per-message heap overhead from internal tracking structures and queue allocation. By multiplexing hundreds of concurrent streams, an attacker forces the Go runtime into an out-of-memory panic, killing the process. The fix in 1.83.1 introduces receive-buffer compaction that coalesces small fragments automatically.
The GitHub Security Advisory (GHSA-vp52-pcj8-j9qc) rates this HIGH, and the GitLab Advisory Database assigns CVSS:3.1 7.5 — both appropriate for an unauthenticated, zero-interaction, zero-complexity remote DoS with no confidentiality or integrity impact. The CVSSv4 score of 8.7 seen in some aggregators is mechanically inflated by the v4 scoring algorithm for network-reachable availability impacts; the 7.5 v3.1 score better reflects operational reality. The DoS-only impact ceiling means this cannot justify CRITICAL unless the affected component canonically serves a fleet-control or identity role — and while gRPC-Go *is* embedded in Kubernetes and service-mesh control planes, the blast radius is service restart, not domain takeover.
5 steps from start to impact.
Locate an exposed gRPC endpoint
content-type: application/grpc) or active (connecting and issuing an HTTP/2 SETTINGS frame). No authentication is required at this stage.- Network path to a gRPC-Go listener (internal LB, public endpoint, or service-mesh sidecar ingress)
- Most enterprise gRPC services sit behind internal load balancers or Kubernetes ClusterIP, not directly internet-facing
- Cloud providers and service meshes often terminate HTTP/2 at the edge proxy (Envoy, nginx), which may have its own frame-handling behavior
content-type headers. Internal scanners can enumerate ports 50051/443 with HTTP/2 probes.Establish HTTP/2 connection and open concurrent streams
MaxConcurrentStreams in gRPC-Go is 100 per connection, but multiple connections can be opened). Each stream begins a gRPC unary or streaming call. No valid credentials or TLS client certificates are needed unless the application explicitly enforces them via interceptors.- HTTP/2 connectivity to the target
- No mandatory client-certificate (mTLS) enforcement at the transport layer
- Services behind Istio/Linkerd with strict mTLS require a valid mesh identity
- Rate-limiting at the LB layer (e.g., Envoy
max_connections,max_requests_per_connection) constrains stream count
Fragment DATA frames into 1-byte payloads
h2 library in Python, or raw net/http2 in Go), the attacker sends DATA frames with 1-byte payloads on each open stream. Each frame is a valid HTTP/2 DATA frame and stays within the flow-control window, so the server's HTTP/2 stack accepts it without back-pressure. The gRPC-Go transport layer allocates a new recvMsg for every frame, each carrying significant per-object heap overhead. No existing weaponized tool is publicly available, but building one from the golang.org/x/net/http2 or Python hyper-h2 library is straightforward — estimated effort is under 50 lines of code.- Ability to craft raw HTTP/2 frames (trivial with any HTTP/2 library)
- Sufficient bandwidth to deliver millions of tiny frames (each frame is ~9 bytes on the wire, so 1 Gbps delivers ~14M frames/sec)
- Some reverse proxies (Envoy, HAProxy) coalesce or buffer DATA frames before forwarding, which would neutralize the fragmentation
- HTTP/2
MAX_FRAME_SIZEsetting does not help — minimum frame size is 1 byte and the spec does not define a *minimum* DATA payload
recvMsg queue depth vs. bytes received.Exhaust heap memory across multiplexed streams
recvMsg in the recvBuffer consumes ~128–256 bytes of heap (slice header, pointer, length, capacity, GC metadata) for 1 byte of actual payload — a 128–256x amplification factor. With 100 concurrent streams per connection and 10 connections, the attacker can force allocation of hundreds of millions of recvMsg objects. The Go garbage collector cannot reclaim these because they are still referenced in the live recvBuffer queues.- No process-level memory limit (cgroup, Kubernetes resource limit) that would trigger OOM-kill before total host exhaustion
- Sustained connection for seconds to minutes depending on available bandwidth
- Kubernetes deployments with
resources.limits.memoryset will OOM-kill the pod, which then restarts — limiting blast to a brief outage rather than host-wide impact GOMEMLIMITcauses aggressive GC but does not prevent allocation, so it slows but does not stop the attack
go_memstats_heap_alloc_bytes) spiking without corresponding request-volume increase; Kubernetes OOMKilled events in pod status.Target process crashes (OOM panic or kernel OOM-kill)
runtime: out of memory or the kernel's OOM killer terminates the process. The gRPC service becomes unavailable. In Kubernetes, the pod restarts but the attacker can repeat the attack to maintain a sustained outage. On bare-metal or VM deployments without memory cgroups, the OOM condition can destabilize co-located processes.- Attack sustained long enough to exhaust available memory (seconds to low minutes at moderate bandwidth)
- Process supervisors (systemd, Kubernetes) restart the service automatically, limiting downtime to seconds per crash cycle
- Attacker must maintain the attack continuously to sustain the outage
container_oom_events_total metric.The supporting signals.
| In-the-Wild Exploitation | No known exploitation. Not listed in CISA KEV. No campaigns or threat-actor activity documented as of 2026-09-20. |
|---|---|
| Proof-of-Concept | No public PoC. However, exploitation is trivial to construct — under 50 lines using Python hyper-h2 or Go x/net/http2. The attack requires only crafting 1-byte HTTP/2 DATA frames, which is a standard library operation. Weaponization timeline: days, not weeks. |
| EPSS Score | 0.00415 (0.415%) — 35th percentile. Indicates low predicted exploitation probability in the next 30 days, consistent with DoS-class vulnerabilities that lack financial incentive for criminal actors. |
| KEV Status | Not listed. No CISA Binding Operational Directive deadline applies. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = 7.5 (HIGH). Network-reachable, zero-complexity, no privileges or interaction required, availability-only impact. Some aggregators report CVSSv4 8.7 — mechanically higher due to v4 scoring methodology changes, not a substantive difference in risk. |
| Affected Versions | google.golang.org/grpc all versions ≤ 1.83.0. This includes every release from the library's inception. Any Go application importing this module at a vulnerable version is affected regardless of Go runtime version. |
| Fixed Versions | 1.83.1 (commits 7354d9c and 8cfeca0, PRs #9331 and #9333). Debian, Ubuntu, and Fedora have begun packaging updated Go modules. Distro backports should track the google.golang.org/grpc module version in go.sum. |
| Scanning & Exposure | gRPC services are not trivially fingerprinted at scale — Shodan/Censys coverage is limited to services responding with application/grpc content-type on standard ports. The vast majority of gRPC-Go deployments are internal microservices behind Kubernetes ClusterIP or internal LBs. Externally exposed gRPC endpoints are a small fraction of the installed base. |
| Disclosure Timeline | GHSA published 2026-08-19 by easwars (gRPC team maintainer). CVE record published 2026-09-01. Fix merged and released same day as GHSA. Indicates coordinated internal discovery, not external researcher report. |
| Researcher / Reporter | easwars — gRPC-Go core maintainer at Google. Internal discovery during transport-layer hardening work. No external researcher credited. |
Why this verdict
- Unauthenticated remote, zero-complexity attack path: The attacker needs nothing but a network path to a gRPC listener — no credentials, no user interaction, no special configuration. CVSS:3.1 AV:N/AC:L/PR:N/UI:N confirms the low barrier. This alone sets a baseline of HIGH for any availability impact.
- Massive installed base amplifies fleet risk: gRPC-Go is one of the most imported Go modules in the ecosystem. It is a transitive dependency of Kubernetes control-plane components, Istio, Consul, Vault agents, Prometheus, Grafana agents, ArgoCD, and thousands of enterprise microservices. A vulnerability in the transport layer affects every application that embeds it.
- DoS-only impact ceiling prevents CRITICAL: The attack produces an OOM crash — not code execution, not data exfiltration, not privilege escalation. The service restarts automatically in supervised environments. This fundamentally caps severity below CRITICAL regardless of deployment breadth.
- Role multiplier: gRPC-Go occupies high-value roles: (1) Kubernetes control plane — kube-apiserver, etcd client, kubelet all use gRPC-Go; an OOM crash here disrupts cluster scheduling and API access, blast radius = cluster-wide service disruption. (2) Service mesh control planes (Istio pilot, Linkerd) — OOM crash disrupts traffic routing for the entire mesh. (3) HashiCorp Vault/Consul — OOM crash temporarily blocks secret retrieval and service discovery. However, in ALL these cases the outcome is *temporary service disruption with automatic restart*, not domain takeover, fleet compromise, data egress, or supply-chain pivot. The high-value-role floor for CRITICAL requires one of those outcomes; DoS alone does not qualify. The floor remains at HIGH.
- No exploitation or PoC in the wild dampens urgency but not severity: EPSS at 0.415% and no KEV listing reflect low current threat activity. However, the trivial exploitability (< 50 LOC) means weaponization lag is measured in days once an attacker is motivated. This does not justify downgrading from HIGH — it means the patch window has not yet closed, not that the window is wide.
- Friction from typical deployment patterns: Most gRPC-Go services are behind internal load balancers, Kubernetes ClusterIP, or service-mesh mTLS — not directly internet-exposed. Reverse proxies (Envoy, nginx) may coalesce frames before forwarding, partially neutralizing the attack. Kubernetes memory limits cause pod restart rather than host-wide impact. These factors prevent upgrading beyond HIGH but are insufficient to justify MEDIUM given the zero-auth, zero-complexity attack vector.
Why not higher?
CRITICAL requires either RCE, authentication bypass, or a chain that terminates in domain/fleet/supply-chain compromise. This vulnerability produces only availability impact — a process crash that is recoverable via automatic restart. Even in the highest-value deployment roles (Kubernetes control plane, service-mesh pilot), the blast radius is temporary cluster disruption, not persistent compromise. The DoS-only impact ceiling is an absolute bar to CRITICAL for this class of vulnerability.
Why not lower?
MEDIUM would understate the risk of an unauthenticated, zero-complexity remote DoS against a library embedded in critical infrastructure at massive scale. The attack requires no privileges, no user interaction, and no special conditions. The memory amplification factor (128–256x) makes it efficient even at low bandwidth. The library's presence in Kubernetes control-plane components and service-mesh infrastructure means a targeted attack against internal services (post-initial-access) could disrupt fleet orchestration. The combination of trivial exploitability, zero-auth access, and high-value-role deployment prevalence floors this at HIGH.
What to do — in priority order.
- Upgrade google.golang.org/grpc to ≥ 1.83.1 in all Go modules — The definitive fix. Run
go get google.golang.org/[email protected]and rebuild. Receive-buffer compaction is enabled by default in 1.83.1, coalescing small frames to eliminate the amplification. Per the noisgate remediation SLA for HIGH, deploy the patched version within 180 days. - Set Kubernetes memory limits on all gRPC-serving pods — Add
resources.limits.memoryto pod specs so OOM-kill is scoped to the pod, not the node. The pod restarts automatically via the kubelet. This converts a potential host-level outage into a brief pod restart. Deploy within 30 days per noisgate mitigation SLA for HIGH. - Enforce connection and stream limits at the reverse proxy — Configure Envoy, nginx, or HAProxy to cap
max_concurrent_streams(e.g., 50),max_connections_per_source_ip, andmax_requests_per_connection. This limits the attacker's ability to open enough streams to trigger OOM. Many service-mesh configurations already enforce these defaults. - Enable mTLS on gRPC listeners — Require mutual TLS at the transport layer so only authenticated clients can establish HTTP/2 connections. In Istio, set
PeerAuthenticationto STRICT mode. This eliminates unauthenticated attackers from the threat model entirely. Does not protect against compromised internal identities. - Monitor process memory and set OOM alerting — Alert on
go_memstats_heap_alloc_bytesexceeding 2x normal baseline and oncontainer_oom_events_totalincrementing. This does not prevent exploitation but ensures rapid detection and response. Pair with runbook for pod restart and attacker-source blocking.
- gRPC keepalive settings (
KeepaliveParams,KeepaliveEnforcementPolicy) — these govern idle-connection timeouts and ping frequency, not DATA frame handling. An active attack sending frames continuously will never trigger keepalive enforcement. GOMEMLIMITenvironment variable — this tells the Go GC to be more aggressive when heap approaches the limit, but it does not *prevent* allocation. The attacker's frames are live-referenced inrecvBufferand cannot be collected.GOMEMLIMITmay slow the OOM by seconds but will not stop it.- HTTP/2 flow-control window tuning — the attack operates *within* configured flow-control windows. Reducing
InitialWindowSizelimits total payload bytes but does not limit the *number* of frames. A 64 KB window can still hold 65,536 one-byte DATA frames, each with full per-message overhead. - Application-level request timeouts (
grpc.ConnectionTimeout, unary interceptor deadlines) — the memory is consumed at the transport layer *before* the application handler sees the request. Timeouts fire too late; therecvBufferis already bloated.
Crowdsourced verification payload.
Run this script on each host or in each container image's CI pipeline where a Go binary imports google.golang.org/grpc. It checks go.sum or the compiled binary's module info. Requires read access to the project directory. Example: bash check_cve_2026_84304.sh /path/to/go/project
#!/usr/bin/env bash
# CVE-2026-84304 Checker — gRPC-Go OOM via HTTP/2 DATA Frame Fragmentation
# Usage: bash check_cve_2026_84304.sh <path-to-go-project-or-binary>
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -uo pipefail
FIXED_VERSION="1.83.1"
TARGET="${1:-}"
if [ -z "$TARGET" ]; then
echo "Usage: $0 <path-to-go-project-or-binary>"
echo "UNKNOWN — no target specified"
exit 2
fi
version_gte() {
# Returns 0 if $1 >= $2 using sort -V
[ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}
# Strategy 1: Check go.sum in a Go module project
if [ -d "$TARGET" ]; then
GOSUM="$TARGET/go.sum"
GOMOD="$TARGET/go.mod"
FOUND_VERSION=""
if [ -f "$GOSUM" ]; then
# Extract the highest grpc-go version from go.sum
FOUND_VERSION=$(grep -oP 'google\.golang\.org/grpc v\K[0-9]+\.[0-9]+\.[0-9]+' "$GOSUM" \
| sort -V | tail -n1)
elif [ -f "$GOMOD" ]; then
FOUND_VERSION=$(grep -oP 'google\.golang\.org/grpc v\K[0-9]+\.[0-9]+\.[0-9]+' "$GOMOD" \
| sort -V | tail -n1)
fi
if [ -z "$FOUND_VERSION" ]; then
echo "UNKNOWN — google.golang.org/grpc not found in $TARGET"
exit 2
fi
if version_gte "$FOUND_VERSION" "$FIXED_VERSION"; then
echo "PATCHED — google.golang.org/grpc v$FOUND_VERSION >= v$FIXED_VERSION"
exit 0
else
echo "VULNERABLE — google.golang.org/grpc v$FOUND_VERSION < v$FIXED_VERSION (CVE-2026-84304)"
exit 1
fi
fi
# Strategy 2: Check a compiled Go binary's embedded module info
if [ -f "$TARGET" ] && file "$TARGET" | grep -qiE 'ELF|Mach-O|PE32'; then
if ! command -v go >/dev/null 2>&1; then
echo "UNKNOWN — 'go' tool not in PATH, cannot inspect binary module info"
exit 2
fi
MOD_INFO=$(go version -m "$TARGET" 2>/dev/null || true)
FOUND_VERSION=$(echo "$MOD_INFO" \
| grep -oP 'google\.golang\.org/grpc\s+v\K[0-9]+\.[0-9]+\.[0-9]+' \
| sort -V | tail -n1)
if [ -z "$FOUND_VERSION" ]; then
echo "UNKNOWN — google.golang.org/grpc not found in binary module info"
exit 2
fi
if version_gte "$FOUND_VERSION" "$FIXED_VERSION"; then
echo "PATCHED — google.golang.org/grpc v$FOUND_VERSION >= v$FIXED_VERSION"
exit 0
else
echo "VULNERABLE — google.golang.org/grpc v$FOUND_VERSION < v$FIXED_VERSION (CVE-2026-84304)"
exit 1
fi
fi
echo "UNKNOWN — target is not a directory or recognized binary: $TARGET"
exit 2- GitHub Security Advisory (GHSA-vp52-pcj8-j9qc)
- GitLab Advisory Database — CVE-2026-84304
- Go Vulnerability Database — GO-2026-6348
- gRPC-Go PR #9331 — Restrict memory overhead of buffering small data frames
- CVE Record — CVE-2026-84304
- CyberStrike CVE Detail — CVE-2026-84304
- DailyCVE — gRPC-Go Heap Memory Exhaustion
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.