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 · 8.8CVE-2026-27775CVE-2026-27775: Gitea Pre-Receive Hook Authorization Bypass via Cached Branch Permission
- high · 8.1CVE-2026-55987CVE-2026-55987: Gitea OAuth2 Sign-In Reactivates Admin-Deactivated Accounts
- high · 8.9CVE-2026-58424CVE-2026-58424: Gitea Actions Fork PR Approval Gate Permanent Bypass
- high · 8.1CVE-2026-58439CVE-2026-58439: Gitea Branch Protection Bypass via PR Retargeting