Raised by a V12 audit of the response-lifetime work and verified against the tree.
What happens
The stream-pair specification requires a responder to answer the ranges of one connection in request order, but nothing enforces it on receipt. The response index distinguishes only missing, unique and ambiguous keys, so both terminal handlers and the matched-body path accept any uniquely keyed live range regardless of position in the request sequence.
A peer that has proven itself once can then leave an early request unanswered, answer a later one, and repeat. Each accepted body resets the peer-wide liveness deadline and records a congestion-control delivery, and each terminal frees request-count headroom for another request.
Why it matters
The sharper consequence is credit misattribution rather than liveness. The one order-aware helper in the routine, which picks the earliest open response, is used only by the discard path. With out-of-order serving undetected, a body that matches no open response is charged to the earliest range rather than to the range actually being served, spending that range's credit and advancing its expected-hash cursor, so the genuine body for that range later fails to match.
The peer does pay for each renewal with a real hash-matched and height-matched block, the starved range's work is returned at its own deadline and refetched elsewhere, and each starved range permanently occupies a slot, so the peer eventually wedges itself. That bounds the damage but does not make the accounting correct.
Suggested fix
Add an earliest-open-response accessor keyed on the monotonic request id, and require the matched-body path and both terminal handlers to match it, rejecting otherwise. The helper is most of the way there already.
There are two shapes the enforcement can take, and the choice matters more than the accessor:
- Reject a body or terminal that does not belong to the earliest open range. Strict, but it adds a new protocol-reject path, so it disconnects any responder that interleaves in a way the spec forbids but that real implementations might still do.
- Treat a later range's body as a mismatched part of the earliest open range, i.e. route it through the existing discard path. No disconnect, and it reuses machinery that already exists. The cost is that it spends the earliest range's credit on a body that was not for it, which is the same misattribution the strict option is meant to remove.
Option 2 is worth evaluating first because it defuses the objection below without a new fault class. Whichever is chosen, the terminals need the same rule as the bodies, or a peer can still cycle terminals to free request-count headroom.
Deliberately not fixed inside the stack that surfaced it: option 1 adds a new protocol-reject path, which can disconnect honest peers if the ordering rule is stated more strictly than responders actually behave, and that stack has already had fuzz-scenario fallout from one such change. It wants its own PR with fuzz coverage for interleaved and withheld terminals.
A later review comment on the originating PR raised the body-matching half of this independently, which is why the issue mentions both sites.
Raised by a V12 audit of the response-lifetime work and verified against the tree.
What happens
The stream-pair specification requires a responder to answer the ranges of one connection in request order, but nothing enforces it on receipt. The response index distinguishes only missing, unique and ambiguous keys, so both terminal handlers and the matched-body path accept any uniquely keyed live range regardless of position in the request sequence.
A peer that has proven itself once can then leave an early request unanswered, answer a later one, and repeat. Each accepted body resets the peer-wide liveness deadline and records a congestion-control delivery, and each terminal frees request-count headroom for another request.
Why it matters
The sharper consequence is credit misattribution rather than liveness. The one order-aware helper in the routine, which picks the earliest open response, is used only by the discard path. With out-of-order serving undetected, a body that matches no open response is charged to the earliest range rather than to the range actually being served, spending that range's credit and advancing its expected-hash cursor, so the genuine body for that range later fails to match.
The peer does pay for each renewal with a real hash-matched and height-matched block, the starved range's work is returned at its own deadline and refetched elsewhere, and each starved range permanently occupies a slot, so the peer eventually wedges itself. That bounds the damage but does not make the accounting correct.
Suggested fix
Add an earliest-open-response accessor keyed on the monotonic request id, and require the matched-body path and both terminal handlers to match it, rejecting otherwise. The helper is most of the way there already.
There are two shapes the enforcement can take, and the choice matters more than the accessor:
Option 2 is worth evaluating first because it defuses the objection below without a new fault class. Whichever is chosen, the terminals need the same rule as the bodies, or a peer can still cycle terminals to free request-count headroom.
Deliberately not fixed inside the stack that surfaced it: option 1 adds a new protocol-reject path, which can disconnect honest peers if the ordering rule is stated more strictly than responders actually behave, and that stack has already had fuzz-scenario fallout from one such change. It wants its own PR with fuzz coverage for interleaved and withheld terminals.
A later review comment on the originating PR raised the body-matching half of this independently, which is why the issue mentions both sites.