Conversation
receive_headers handled every HEADERS block, trailers included, and called _initialize_content_length on all of them. _track_content_length was only ever called from receive_data, so a stream ended by a trailers section never reached the "end_stream and expected != actual" branch: - trailers without content-length left the expectation as None and the guard was skipped entirely; - trailers with content-length silently replaced the header-section value, so a peer could declare 10 in the headers and 3 in the trailers and have a 13-byte body accepted. RFC 9113 section 8.1.1 makes a message malformed when content-length does not equal the sum of the DATA payload lengths, and the exemptions it lists are 204, 304 and HEAD, not trailers. The too-much-data direction still errored, because it trips while receiving DATA, so only the short-body direction was silently accepted. Run the same parse on trailers so an invalid content-length there is still a ProtocolError, then put the previous expectation back, and validate the body where the stream actually ends.
|
I am not sure if I understand the problem stated here. To my understanding, a If a HEADERS frame with END_STREAM set is received after DATA, the content length is already known or final, so there is no need to parse or validate it again. So the only missing call is possibly |
|
Thanks for the review. The length check now runs before |
| c.clear_outbound_data_buffer() | ||
|
|
||
| trailers = frame_factory.build_headers_frame( | ||
| headers=[("content-length", "banana")], |
There was a problem hiding this comment.
Trailers are not allowed to have content-length headers at all - no matter their value.
| c.clear_outbound_data_buffer() | ||
|
|
||
| trailers = frame_factory.build_headers_frame( | ||
| headers=[("content-length", "13"), ("x-checksum", "0")], |
There was a problem hiding this comment.
Trailers are not allowed to have content-length headers at all - no matter their value.
|
@Kriechi good catch — trailers must not carry That made the receive-side trailer fixtures in Still the same fix underneath: a body that ends with trailers is policed where the stream actually ends. |
|
Both of these are addressed in
The rest of the change is separate from that rule and is what the earlier commits do: a trailers section that ends a stream without a If the RFC 9110 § 6.5.1 reading is not what you want for the mismatch cases — i.e. if you would rather a |
Fixes #1328
Description
A stream that ends with a trailers section never had its
content-lengthpoliced, and acontent-lengthin the trailers was accepted as if it were the one from the header section.H2Stream.receive_headershandles every received HEADERS block, trailers included, and called_initialize_content_length(headers)unconditionally._track_content_length, where the comparison actually happens, was only ever called fromreceive_data, soend_streamwas neverTruefor a body that ends with trailers. A peer declaringcontent-length: 15could send 13 bytes and then a trailers section, and the short body was accepted.Per @Kriechi's review,
content-lengthis now rejected in a trailers section outright: RFC 9110 § 6.5.1 only allows trailer fields whose definition permits them there, andcontent-lengthhas to be evaluated before the content is received. Trailers that end a stream run the same length check, so they can neither reset nor redefine the expectation.Test plan
masteratbc239af1d1b85bc70482804f30a0e0e587d90a08(4.4.1), Python 3.14, macOS. Unit level with the existingframe_factoryfixture, in-memory bytes, no network.TestContentLengthEnforcedAtTrailersintests/test_invalid_content_lengths.py: insufficient data ended by trailers, no data at all ended by trailers,content-lengthin trailers rejected for several values, a matching body ended by trailers still accepted withTrailersReceived, and a request with nocontent-lengthunaffected. Four receive-side tests intests/test_basic_logic.pyusedcontent-length: 0as their trailer field and now usex-checksum.Command output
Before the fix, with only the tests added:
The two that passed before the fix as well are the ones pinning that valid trailers and a stream with no
content-lengthare still accepted.After: