match chunked as the final coding in is_chunked_transfer_encoding#2487
match chunked as the final coding in is_chunked_transfer_encoding#2487metsw24-max wants to merge 2 commits into
Conversation
|
@metsw24-max thank you, but this PR causes CI errors. |
Headers is an unordered_multimap, whose iteration order for repeated keys is unspecified, so walking equal_range to pick the last line was unreliable and failed CI on libstdc++. Read the Transfer-Encoding value and match its last comma-separated coding token instead. Drop the multi-line ordering case from the unit test, since the container cannot represent received order across duplicate field lines.
|
Sorry about that. The failing assertion was one I added: it fed two separate Transfer-Encoding lines and expected the last-received one to win, but Headers is an unordered_multimap, so the order of duplicate keys isn't defined and libstdc++ iterates them the other way round from libc++. I've dropped that case and the helper now just matches the last coding token of the Transfer-Encoding field value, which is the part that actually mattered for the smuggling case. Passes locally on the no-tls and split builds now. |
|
@metsw24-max Thanks again for spotting this. I picked up the fix and opened #2500, which keeps your core idea (match chunked as the final coding, and route The points that led me to a different implementation:
#2500 handles the multi-line case deterministically instead: it scans every |
is_chunked_transfer_encoding compared the whole Transfer-Encoding field
value against "chunked", so a message whose final coding is chunked but
which names another coding first ("gzip, chunked", valid under RFC 9112
6.1) was read as unframed. On a keep-alive server the body was then left
in the socket and parsed as a smuggled request; open_stream repeated the
check case-sensitively with a raw ==, desyncing the client stream the
same way.
Rework the helper to match the last coding token case-insensitively and
route open_stream through it so both paths agree. The codings may also be
split across multiple Transfer-Encoding lines (RFC 9110 5.3); since
Headers is an unordered_multimap whose duplicate-key iteration order is
not portable, the final coding of a multi-line field cannot be
determined reliably, so treat any such message that names chunked as
chunked (fail safe: a mis-parse only closes the connection, whereas the
opposite error enables smuggling). A unit test covers the helper,
including order-independent multi-line cases.
Based on #2487 by @metsw24-max.
the server and streaming client only treat a message as chunk-framed when the whole Transfer-Encoding value equals "chunked", so a message whose final coding is chunked but which names another coding first ("gzip, chunked", valid under RFC 9112 6.1 where chunked must be the final coding) is missed. is_chunked_transfer_encoding matches the entire field against "chunked", and open_stream repeats the check case-sensitively with a raw ==. on the server such a request is read as bodiless and its body is left in the socket for a keep-alive connection to parse as the next request; on the client a "Transfer-Encoding: Chunked" response desyncs the stream the same way. this reworks is_chunked_transfer_encoding to compare the last coding token of the last Transfer-Encoding line case-insensitively (RFC 9110 5.3 folds repeated lines into one ordered list) and routes open_stream through it so both paths agree. a unit test covers the helper.