Skip to content

http2: HEADERS without END_STREAM after the block that opens a request resets the stream, so its fields no longer land in the body - #512

Open
MDA2AV wants to merge 1 commit into
mainfrom
fix/502-http2-headers-mid-body
Open

MDA2AV wants to merge 1 commit into
mainfrom
fix/502-http2-headers-mid-body

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

What was wrong

  • A HEADERS frame on a stream whose request is still arriving was taken as another header block, whatever its flags. On a buffered stream its fields were decoded into the arena the body was being appended to.
  • AppendBody grows one range and assumes the body is contiguous. The range then ran across those header bytes, and as many bytes fell off the body's end.
  • A POST with 400 B of DATA, then x-a: abc in a HEADERS without END_STREAM, then 400 B more reached its handler with a body of 800 B, x-aabc at offset 400, and its last 6 bytes missing.

RFC 9113 8.1 makes such a request malformed. Details are in #502.

The fix

A HEADERS without END_STREAM on a buffered stream that is already open resets that stream with PROTOCOL_ERROR. Its block is still decoded, to keep HPACK in step, through the path that already does that for a refused stream's block.

A streamed body already decodes such a block aside and drops it, so that path is unchanged.

Behaviour

API

None.

Tests

New in the Unit suite (Http2TrailerTests.cs):

  • h2 buffered request: HEADERS without END_STREAM after the opening block resets the stream, and its body never reaches a handler
  • h2 buffered request: HEADERS without END_STREAM split over CONTINUATION still keeps HPACK in step
    • Both then send a request that refers to a field the dropped block inserted into the table, and check that it decodes.

Against main's ioxide.http2 (the tests only):

FAIL  h2 buffered request: HEADERS without END_STREAM after the opening block resets the stream, and its body never reaches a handler: expected [1 PROTOCOL_ERROR], got []
FAIL  h2 buffered request: HEADERS without END_STREAM split over CONTINUATION still keeps HPACK in step: expected [1 PROTOCOL_ERROR], got []
6 passed, 2 failed, 0 skipped

Each was also run against a mutation of the code it guards and failed there: no reset, and the block not decoded (COMPRESSION_ERROR on the next request).

With the fix:

  • Unit: 147 passed.
  • E2E: 453 passed.
  • Chaos: 83 passed.
  • Http: 95 passed.

Bench

None. The new branch is on a HEADERS frame for a stream already open, which a normal request only reaches for its trailers.

Fixes #502.

…t resets the stream, so its fields no longer land in the body

They were decoded into the arena the body was being appended to, and the body's one range ran across them, losing as many bytes off its end. RFC 9113 8.1 makes such a request malformed. The block is still decoded, to keep HPACK in step.

Fixes #502.

This branch has not been deployed

No deployments
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.

http2: HEADERS without END_STREAM between DATA frames lands in the request's body and corrupts it

1 participant