Skip to content

rpc_util: limit decompression size in legacy gzipDecompressor to prevent OOM#9114

Open
evilgensec wants to merge 2 commits into
grpc:masterfrom
evilgensec:fix/legacy-decompressor-oom
Open

rpc_util: limit decompression size in legacy gzipDecompressor to prevent OOM#9114
evilgensec wants to merge 2 commits into
grpc:masterfrom
evilgensec:fix/legacy-decompressor-oom

Conversation

@evilgensec
Copy link
Copy Markdown

@evilgensec evilgensec commented May 7, 2026

Summary

The legacy gzipDecompressor.Do() (used when a server is configured with the deprecated grpc.RPCDecompressor(grpc.NewGZIPDecompressor()) option) calls io.ReadAll(z) with no size bound. The size limit check happens after the full payload is materialized in memory:

// decompress():
uncompressed, err := dc.Do(r)          // io.ReadAll — full payload in memory
...
if len(uncompressed) > maxReceiveMessageSize {   // too late

A client can send a highly compressed gRPC frame (e.g., 1 KiB of gzip expanding to 1+ GiB) and force the server to buffer the entire expanded payload before the MaxRecvMsgSize limit fires.

The modern encoding.Compressor path already uses io.LimitReader(dcReader, limit+1) to prevent this (lines 993–1010). The deprecated path did not have the equivalent guard.

Fix

Wrap the reader passed to dc.Do with io.LimitReader(maxReceiveMessageSize+1), matching the behavior of the safe code path.

Scope

Only affects servers explicitly configured with grpc.RPCDecompressor(grpc.NewGZIPDecompressor()). The default server uses the encoding.Compressor path and is unaffected.

RELEASE NOTES:

  • TBD

…ent OOM

The legacy gzipDecompressor.Do (used when the server is configured with
the deprecated grpc.RPCDecompressor option) calls io.ReadAll(z) with no
bound, materializing the entire decompressed payload in memory before
decompress() can check len(uncompressed) > maxReceiveMessageSize.

A client can send a highly compressed gRPC frame (e.g. 1 KiB of gzip
that expands to 1 GiB) and force the server to allocate and fill a
gigabyte buffer before the size limit fires. The default server path
(encoding.Compressor) already uses io.LimitReader(limit+1) to prevent
this; the deprecated path did not.

Fix: wrap the reader passed to dc.Do with io.LimitReader(maxReceiveMessageSize+1)
so that decompression stops at the limit boundary. The existing
len(uncompressed) > maxReceiveMessageSize check then fires as before,
but no more than maxReceiveMessageSize+1 bytes are ever buffered.
Copilot AI review requested due to automatic review settings May 7, 2026 06:22
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the deprecated grpc.Decompressor (legacy gzipDecompressor.Do() / grpc.RPCDecompressor(grpc.NewGZIPDecompressor())) receive path against decompression bombs by ensuring decompression cannot expand unbounded in memory before MaxRecvMsgSize is enforced.

Changes:

  • Wrap the legacy decompressor input reader with io.LimitReader(maxReceiveMessageSize+1) to bound decompression output prior to the post-decompression size check.
  • Add explanatory comments documenting the OOM risk and rationale for the limit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rpc_util.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.02%. Comparing base (3d0dd1e) to head (ee97a0a).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9114      +/-   ##
==========================================
- Coverage   83.06%   83.02%   -0.04%     
==========================================
  Files         413      413              
  Lines       33487    33485       -2     
==========================================
- Hits        27816    27802      -14     
- Misses       4245     4251       +6     
- Partials     1426     1432       +6     
Files with missing lines Coverage Δ
rpc_util.go 83.11% <100.00%> (+0.13%) ⬆️

... and 25 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@easwars easwars self-assigned this May 12, 2026
@easwars easwars added the Type: Security A bug or other problem affecting security label May 12, 2026
@easwars easwars added this to the 1.82 Release milestone May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Security A bug or other problem affecting security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants