bryancall opened a new pull request, #13782:
URL: https://github.com/apache/trafficserver/pull/13782

   When an HTTP/2 origin sends a response with no body (HEADERS with 
END_STREAM) and a header block larger than one IOBuffer block (about 4 KB), ATS 
fails to parse it. The client gets a 502 instead of the origin's 3xx, 204 or 
200. Fixes #13778.
   
   ## Cause
   
   `Http2Stream::send_headers` prints the decoded header into `_receive_buffer` 
one 4 KB block at a time, so a large header spans several blocks and a field 
can be cut at a block boundary. With END_STREAM on an outbound stream it 
signals `VC_EVENT_EOS`, and `HttpSM::state_read_server_response_header` parses 
with `eof = true`.
   
   `HTTPHdr::parse_resp` (and `parse_req`) walk the reader one block at a time 
and passed that same `eof` to the parser for every block. When a field is cut 
at the end of the first block, `MIMEScanner::get` treats it as the end of input 
and returns `ParseResult::ERROR` (unterminated field). The block loop has been 
like this since the initial import. HTTP/1.1 origins rarely hit it because the 
header is normally parsed incrementally on READ_READY (eof false) before any 
EOS shows up.
   
   There are two failure modes:
   
   - Most responses go to `handle_server_setup_error`, get retried, and the 
client gets a 502.
   - A 302 that already has a `Location` field takes the old "allow badly 
formed redirects" path in `HttpSM`, so ATS forwards a truncated header and 
sends the rest of the origin's header block to the client as the response body.
   
   ## Fix
   
   In `parse_resp` and `parse_req`, eof is only passed to the parser for the 
last block that holds data (`eof && b_avail >= r->read_avail()`). A field cut 
at a block boundary is carried into the next block the same way it is when eof 
is false, which is how the incremental HTTP/1.1 path already works.
   
   I fixed this in the parser loop instead of making `Http2Stream` write the 
header into one block, because the loop is wrong for any reader that holds more 
than one block at EOF. That covers every caller, not only HTTP/2.
   
   ## Testing
   
   - New Catch2 test in `test_http2` prints a header into 4 KB blocks the same 
way `send_headers` does, then parses it with eof set. It covers a response and 
a request with a field cut at a block boundary, plus a response whose field 
ends exactly at a block boundary (before the fix that one returned DONE early 
and silently dropped the fields after it).
   - New AuTest `h2origin_large_bodyless_header` uses an HTTP/2 origin (Proxy 
Verifier) to cover a bodyless 302 and a bodyless 204 with about 8.7 KB of 
headers (one large `Content-Security-Policy` plus several smaller fields), 
bodyless 200s just under and just over 4 KB, and a 200 with a body and a large 
header. It checks that the client gets the origin's status and every header 
value.
   - The same AuTest also covers much larger header blocks with a small Python 
HTTP/2 origin (`h2_large_header_origin.py`), because Proxy Verifier's nghttp2 
won't send a header block over 64 KiB. It runs bodyless 302s and 204s of 16 KB 
and 28 KB under the default limits, and 64 KB, 128 KB, 256 KB, 512 KB and 900 
KB with `response_header_max_size`, `http2.max_header_list_size` and 
`header_field_max_size` raised. It also covers a 512 KB response with a body, 
and a 64 KB block at the default limits, which must still be rejected with a 
502.
   
   Before the fix, on both macOS and Linux ASan builds, the unit test fails for 
both parse_resp and parse_req. In the AuTest the 204 and the over-4 KB 200 get 
a 502, and the 302 arrives without its `Content-Security-Policy` header and 
with the rest of the header block as a chunked body. The larger cases fail the 
same way. Every bodyless 204 gets a 502, and a 900 KB 302 reaches the client 
with 201 bytes of header and the other 917 KB as its body. After the fix, both 
tests pass, along with the full ctest suite and the existing `h2` AuTests.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to