gRPC-Go: xDS RBAC Authorization Bypass, HTTP/2 Rapid Reset DoS, and NOT-Rule Panic
Three bugs in gRPC-Go's xDS RBAC engine and HTTP/2 transport let attackers bypass access controls, crash the server with a crafted policy, or exhaust CPU with a stream-reset flood.
The problem
grpc-go versions before 1.82.1 have three related security bugs.
First, the xDS RBAC policy builder silently ignored unsupported permission and principal rule types (specifically Metadata and RequestedServerName), treating them as no-ops. Any policy that relied on those matchers for access control was effectively removed, causing fail-open authorization (CVSS 8.2).
Second, the HTTP/2 transport's Rapid Reset mitigation only counted control frames that were actually written to the wire. A flood of HEADERS + RST_STREAM pairs queued items in the control buffer without hitting that threshold, bypassing backpressure and causing sustained high CPU (CVSS 7.5).
Third, when the RBAC policy translator recursively built matchers for a NOT rule wrapping an unsupported field (such as SourcedMetadata), the recursive call returned a nil matcher. The engine then dereferenced that nil pointer on the next incoming request, panicking and crashing the server process (CVSS 5.9).
Proof of concept
A working proof-of-concept for this issue in google.golang.org/grpc, with the exact payload below.
# 1. xDS RBAC Authorization Bypass (fail-open)
# Push this LDS/RDS policy via a malicious or misconfigured xDS management server.
# The sole permission rule uses Metadata, which grpc-go < 1.82.1 silently drops.
# Result: the rule evaluates as if no restriction exists, granting access to all callers.
permission:
metadata:
filter: "envoy.filters.http.rbac"
path:
- key: "role"
value:
string_match:
exact: "admin"
# 2. NOT-rule panic (server crash)
# Push this RBAC policy. The NOT wrapper around the unsupported SourcedMetadata
# field causes the recursive matcher builder to return nil.
# grpc-go dereferences the nil matcher on the first authorized request -> panic.
permission:
not_rule:
requested_server_name:
exact: "ignored-field"
# 3. HTTP/2 Rapid Reset DoS (high CPU)
# Send a rapid stream-creation + immediate reset flood on the same connection.
# Each HEADERS frame opens a new stream; the matching RST_STREAM cancels it
# before a response is written, so grpc-go < 1.82.1 never counts it against
# the control-frame threshold and never blocks the reader goroutine.
# Example using h2load (or any raw HTTP/2 client):
# h2load -n 100000 -c 1 -m 1000 --header=':method: POST' \
# --header=':path: /Service/Method' \
# --header='content-type: application/grpc' \
# https://target:443 &
# followed immediately by RST_STREAM (error_code=CANCEL) for each stream.For the RBAC bypass: the policy-translation switch statement in internal/xds/rbac had no case for Metadata or RequestedServerName matchers, so they fell through to a no-op return. The patch adds explicit case handlers that return an error, causing the entire policy to be rejected rather than silently weakened (CWE-285 / CWE-703).
For the NOT-rule panic: the recursive buildMatcher call for the inner rule of a NOT node could return (nil, nil) for unknown field types. The caller did not check for a nil matcher before wrapping it in a NotMatcher, so authorization of any real request immediately dereferenced the nil pointer (CWE-248).
The fix propagates an error upward instead.
For the HTTP/2 Rapid Reset DoS: the pre-patch threshold check only fired for frames that produced outbound control frames (RST_STREAM from the server, SETTINGS ACKs). Client-initiated RST_STREAM resets that abort streams mid-flight never incremented the counter, so a high-frequency HEADERS+RST_STREAM loop bypassed backpressure entirely (CWE-400).
The fix counts client-aborted streams toward the same threshold.
The fix
Upgrade google.golang.org/grpc to **v1.82.1** or later. The fix is in commit 4ea465d4ab98013f72a142fe0fc89c19770b2935 (PR #9236). If immediate upgrade is not possible: (a) ensure your xDS management server never pushes RBAC policies containing Metadata, RequestedServerName, or NOT rules around unsupported fields; (b) front the gRPC server with a reverse proxy (e.g., Envoy) configured with strict max_concurrent_streams and RST_STREAM rate limits.
Related research
- high · 7.5CVE-2026-54638CVE-2026-54638: gotd/td Pre-Auth Denial of Service via Unbounded Memory Allocation
- high · 7.5CVE-2026-54719CVE-2026-54719: goshs .goshs ACL Bypass via ?bulk Zip-Download Route
- highCVE-2026-33413: etcd Watch API RBAC Authorization Bypass via Open-Ended Range
- highetcd tlsListener Unbounded TLS Handshake Goroutine DoS