Skip to content

Ensure body reads only return 0-length at the end #483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 29, 2025

Conversation

cceckman-at-fastly
Copy link
Contributor

Tests and a slightly broader preventative measure for #479.

The Rust Read trait uses a return value of Ok(0) to indicate a (permanent/temporary) end-of-stream, e.g. end-of-file.
We use the same convention for the fastly_http_body::read hostcall.

The Body trait is a little more careful, with a separate is_stream_end method distinct from the "length of frame" property.

However, this means if our Body implementations ever yield a zero-length chunk, the fastly_http_body::read hostcall will look like an end-of-stream to the guest. That's at the root of #479.

#480 addresses the observed cause- that the gzip decoder in particular can yield zero-length chunks when the header + body are split. However, it's in principle possible for another (current or future) variant of Body to reintroduce the issue.

  • Test for zero-length bodies, as an integraton test and as a property test. If fix(body): skip empty chunks during gzip decoding #480 is patched out, both of these groups of tests reproduce the issue with the gzip body.
  • Tackle the issue where it occurs, at the conversion from the http::Body stream-style API to the std::io::Read-style API. Add a Body::read method that acts more like the AsyncRead traits; use it from the hostcalls, instead of putting Body internals in those hostcalls.

Fixed #479 (to my satisfaction)

This demonstrates the underlying issue in #479. The origin writes
maliciously-sized chunks - zero-length for the uncompressed body, two
bytes at a time for the compressed body - and the test fixture asserts
that the expected data are read.

This passes for the uncompressed body (yay!); without #480, this fails
for the uncompressed body (i.e. it's a reproducer for #479).
A more general set of tests for #479. Assume that the origin can send
chunks of arbitrary length - in the case of #479, a gzip header in a
separate chunk from the content.

These show up as separate chunks in a `hyper::Body`. Ensure that, when
splitting a body at arbitrary chunk lengths, the output still reproduces
the body. This causes failures without #480 (or similar changes).

This validates the fix in #480 (the gzip test fails without #480).
Issue 479 has more details on the "zero-length chunk" issue. It comes
from an API divergence between `http::Body`, which has an explicit "end
of stream" signal, and `std::io::Read` (and its async analogues), which
use "zero bytes read" to indicate an end-of-stream. Providing a
zero-length chunk results in confusion when moving from the HTTP API
to the Read API, as we do in `fastly_http_body::read`.

Modeled after the code in Compute Platform, here we introduce an
analogue to the AsyncRead traits. The helper `check_body_read`
shows how to safely use this to read the whole body, as with AsyncRead
users. The HTTP body read hostcalls are updated to use the new reader,
hiding details of `Body` and sending them through the happy path.

This is an alternative fix for #479, that should cover any lingering
bugs in existing/future `Body` implementations as well.
@cceckman-at-fastly cceckman-at-fastly requested a review from acme May 28, 2025 19:39
@cceckman-at-fastly cceckman-at-fastly marked this pull request as ready for review May 28, 2025 19:39
Copy link
Collaborator

@acme acme left a comment

Choose a reason for hiding this comment

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

👍 Thanks. I'm not very familiar with the codebase, but this looks good.

@cceckman-at-fastly cceckman-at-fastly merged commit c5528b1 into main May 29, 2025
15 checks passed
@cceckman-at-fastly cceckman-at-fastly deleted the cceckman/body-read branch May 29, 2025 20:39
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.

Zero-length chunk in Body generates premature body end (set_auto_decompress_gzip bug)
2 participants