Skip to content

grpc_http1_reverse_bridge: fix SEGFAULT when buffering large response without response_size_header#45880

Open
relvira wants to merge 2 commits into
envoyproxy:mainfrom
relvira:fix-grpc-reverse-bridge-streaming-upstream
Open

grpc_http1_reverse_bridge: fix SEGFAULT when buffering large response without response_size_header#45880
relvira wants to merge 2 commits into
envoyproxy:mainfrom
relvira:fix-grpc-reverse-bridge-streaming-upstream

Conversation

@relvira

@relvira relvira commented Jun 29, 2026

Copy link
Copy Markdown

Description:

Fixes #45860

When withhold_grpc_frames is enabled without response_size_header, the filter buffers the entire upstream response body and releases it all in a single encodeData() call on end_stream. For responses larger than initial_stream_window_size (default 16MB since #40716), this causes the H2 codec to queue thousands of DATA frames in a single sendPendingFrames() call. The write buffer exceeds the high watermark during the frame burst, triggering callbacks that interact with the stream mid-encode, resulting in memory corruption and a SEGFAULT at Http2::ConnectionImpl::StreamImpl::encodeData().

This bug is latent since the filter's introduction but was previously masked by the 256MB default stream window. Commit 9ad6cf1 (#40716) reduced the default to 16MB, exposing the crash for any response >16MB.

This fix uses the Content-Length header from upstream HTTP/1.1 responses to enable the existing streaming path (previously only available via response_size_header config). Data now flows through in chunks as received from the upstream, avoiding the frame burst that crashes the H2 codec.

Responses without Content-Length continue to use the existing buffering path (no behavior change).

Risk Level: Low — only activates when Content-Length is present and withhold_grpc_frames=true without response_size_header. Existing behavior unchanged for all other cases.

Testing:

  • Updated existing unit tests to reflect streaming behavior when Content-Length is present
  • Added WithholdGrpcFramesStreamsWithContentLength test — validates streaming happy path
  • Added WithholdGrpcFramesContentLengthMismatch test — validates error on size mismatch
  • GrpcRequestNoContentLength test unchanged — confirms fallback to buffering

AI disclosure: Claude (Anthropic) was used to assist with writing this patch and tests. I have reviewed and understand all submitted code.

Signed-off-by: Rafael Elvira me@relvira.org

@repokitteh-read-only

Copy link
Copy Markdown

Hi @relvira, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #45880 was opened by relvira.

see: more, trace.

@relvira relvira force-pushed the fix-grpc-reverse-bridge-streaming-upstream branch from 832583b to a9ed2a4 Compare June 29, 2026 10:07
@relvira relvira had a problem deploying to external-contributors June 29, 2026 10:07 — with GitHub Actions Error
@relvira relvira force-pushed the fix-grpc-reverse-bridge-streaming-upstream branch from a9ed2a4 to f074569 Compare June 29, 2026 10:14
@relvira relvira temporarily deployed to external-contributors June 29, 2026 10:14 — with GitHub Actions Inactive
@relvira relvira changed the title fix(grpc_http1_reverse_bridge): stream response when Content-Length is available grpc_http1_reverse_bridge: fix SEGFAULT when buffering large response without response_size_header Jun 29, 2026
uint64_t content_length = 0;
if (!length_header.empty() && absl::SimpleAtoi(length_header, &content_length) &&
content_length > 0) {
response_message_length_ = static_cast<uint32_t>(content_length);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think changing response_message_length_ to uint64_t might be doable, and gets rid of the cast. (Not sure if that requires changing upstream_response_bytes_ too.)

@yanavlasov yanavlasov self-assigned this Jun 29, 2026
@yanavlasov

Copy link
Copy Markdown
Contributor

@relvira format check is complaining: https://github.com/envoyproxy/envoy/actions/runs/28396207097/job/84135494358#step:21:755

/wait

…s available

When withhold_grpc_frames is enabled without response_size_header, the
filter previously buffered the entire response body and released it in a
single encodeData call on end_stream. For large responses (e.g. 154MB),
this generates ~9,600 H2 DATA frames in one burst, which can trigger
memory corruption via write buffer high watermark callbacks during the
Send loop, causing a SEGFAULT in Http2::ConnectionImpl::StreamImpl::encodeData().

This fix uses the Content-Length header from HTTP/1.1 upstream responses
to enable the existing streaming path (previously only available via
response_size_header config). Data now flows through in chunks as
received, avoiding the frame burst that crashes the H2 codec.

Responses without Content-Length continue to use the buffering path.

Signed-off-by: Rafael Elvira <me@relvira.org>
@relvira relvira force-pushed the fix-grpc-reverse-bridge-streaming-upstream branch from f074569 to 2270398 Compare June 30, 2026 11:47
@relvira relvira temporarily deployed to external-contributors June 30, 2026 11:47 — with GitHub Actions Inactive
Upstream changed route() return type to OptRef, so Return(nullptr)
no longer compiles.

Signed-off-by: Rafael Elvira <me@relvira.org>
@relvira relvira requested a deployment to external-contributors July 2, 2026 09:30 — with GitHub Actions Waiting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

grpc_http1_reverse_bridge: crash when withhold_grpc_frames buffers large response and H2 stream window is smaller than response

3 participants