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]
