← Back to Feed CACHED · 2026-09-20 06:28:41 · CACHE_KEY CVE-2026-84304
CVE-2026-84304 · CWE-400 · Disclosed 2026-09-01

gRPC-Go is the Go language implementation of gRPC.

ASSESSED — NOISGATE
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

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.

"Unauthenticated OOM kill on any reachable gRPC-Go service — trivial to weaponize, DoS-only impact"
02 · The Attack Path

5 steps from start to impact.

STEP 01

Locate an exposed gRPC endpoint

The attacker identifies a service accepting gRPC (HTTP/2 on ports 443, 8443, 50051, or any custom listener). Service discovery can be passive (Shodan, Censys scanning for content-type: application/grpc) or active (connecting and issuing an HTTP/2 SETTINGS frame). No authentication is required at this stage.
Conditions required:
  • Network path to a gRPC-Go listener (internal LB, public endpoint, or service-mesh sidecar ingress)
Where this breaks in practice:
  • 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
Detection/coverage: Shodan/Censys can identify exposed gRPC services via HTTP/2 ALPN and content-type headers. Internal scanners can enumerate ports 50051/443 with HTTP/2 probes.
STEP 02

Establish HTTP/2 connection and open concurrent streams

The attacker initiates an HTTP/2 connection and opens the maximum allowed concurrent streams (default 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.
Conditions required:
  • HTTP/2 connectivity to the target
  • No mandatory client-certificate (mTLS) enforcement at the transport layer
Where this breaks in practice:
  • 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
Detection/coverage: Connection-rate anomalies detectable by WAF/reverse-proxy access logs or network flow telemetry.
STEP 03

Fragment DATA frames into 1-byte payloads

Using a custom HTTP/2 client (e.g., modified 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.
Conditions required:
  • 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)
Where this breaks in practice:
  • Some reverse proxies (Envoy, HAProxy) coalesce or buffer DATA frames before forwarding, which would neutralize the fragmentation
  • HTTP/2 MAX_FRAME_SIZE setting does not help — minimum frame size is 1 byte and the spec does not define a *minimum* DATA payload
Detection/coverage: Anomaly detection on HTTP/2 frame counts vs. payload size ratio; gRPC server metrics showing disproportionate recvMsg queue depth vs. bytes received.
STEP 04

Exhaust heap memory across multiplexed streams

As frames accumulate, the Go runtime heap grows without bound. Each 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.
Conditions required:
  • 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
Where this breaks in practice:
  • Kubernetes deployments with resources.limits.memory set will OOM-kill the pod, which then restarts — limiting blast to a brief outage rather than host-wide impact
  • GOMEMLIMIT causes aggressive GC but does not prevent allocation, so it slows but does not stop the attack
Detection/coverage: Process memory metrics (Prometheus go_memstats_heap_alloc_bytes) spiking without corresponding request-volume increase; Kubernetes OOMKilled events in pod status.
STEP 05

Target process crashes (OOM panic or kernel OOM-kill)

The Go runtime either panics with 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.
Conditions required:
  • Attack sustained long enough to exhaust available memory (seconds to low minutes at moderate bandwidth)
Where this breaks in practice:
  • 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
Detection/coverage: Process crash logs, systemd journal entries, Kubernetes CrashLoopBackOff status, alerting on container_oom_events_total metric.
03 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNo known exploitation. Not listed in CISA KEV. No campaigns or threat-actor activity documented as of 2026-09-20.
Proof-of-ConceptNo 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 Score0.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 StatusNot listed. No CISA Binding Operational Directive deadline applies.
CVSS VectorCVSS: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 Versionsgoogle.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 Versions1.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 & ExposuregRPC 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 TimelineGHSA 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 / Reportereaswars — gRPC-Go core maintainer at Google. Internal discovery during transport-layer hardening work. No external researcher credited.
04 · The Call

Final Verdict
= UNCHANGED to HIGH (7.5/10)

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.

05 · Compensating Control

What to do — in priority order.

  1. 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.
  2. Set Kubernetes memory limits on all gRPC-serving pods — Add resources.limits.memory to 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.
  3. 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, and max_requests_per_connection. This limits the attacker's ability to open enough streams to trigger OOM. Many service-mesh configurations already enforce these defaults.
  4. Enable mTLS on gRPC listeners — Require mutual TLS at the transport layer so only authenticated clients can establish HTTP/2 connections. In Istio, set PeerAuthentication to STRICT mode. This eliminates unauthenticated attackers from the threat model entirely. Does not protect against compromised internal identities.
  5. Monitor process memory and set OOM alerting — Alert on go_memstats_heap_alloc_bytes exceeding 2x normal baseline and on container_oom_events_total incrementing. This does not prevent exploitation but ensures rapid detection and response. Pair with runbook for pod restart and attacker-source blocking.
What doesn't work
  • 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.
  • GOMEMLIMIT environment 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 in recvBuffer and cannot be collected. GOMEMLIMIT may 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 InitialWindowSize limits 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; the recvBuffer is already bloated.
06 · Verification

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

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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
07 · Sources

Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.