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

   Ten guards in `src/proxy/http3/QPACK.cc` are written
   
   ```c
   if ((ret = xpack_decode_integer(tmp, ...)) < 0 && tmp > 0xFFFF) {
     return -1;
   }
   ```
   
   With `&&`, neither condition rejects on its own: a decode failure falls 
through
   because the value check does not hold, and an in-range failure-free decode 
of an
   oversized varint falls through because the error check does not hold. The 
value
   is then narrowed into the surrounding `uint16_t`. The `delta_base_index` 
guard in
   `_decode_header()` additionally had its comparison inverted
   (`delta_base_index < 0xFFFF` where `>` was meant).
   
   Lines: 287, 925, 934, 1517, 1524, 1548, 1555, 1578, 1600, 1622.
   
   ### One bound change, and why it is required
   
   The value guard in `_read_insert_with_name_ref()` (line 1525 on master) is 
the
   only one of the ten bounded at `0xFF`; the other nine use `0xFFFF`. This 
raises
   it to `0xFFFF` as well, and that is load bearing rather than tidying:
   
   `value_len` is a `size_t &`, so `0xFF` is not protecting against any 
narrowing --
   it is a bare sanity limit on a decoded header value length. Under `&&` it 
never
   fired, so it did not matter. Under `||` it fires, and a `0xFF` bound would 
abort
   the decode for **any header value longer than 255 bytes** -- ordinary 
cookies and
   tokens included. At `0xFFFF` the check is unreachable, because
   `xpack_decode_string()` is already capped by `_header_field_max_size` (32768 
by
   default), which matches the behaviour of the other nine sites.
   
   Flipping that operator without raising the bound would be a regression, so 
the
   two changes belong together.
   
   ### The unchecked failure is the more interesting half
   
   On failure `xpack_decode_integer()` returns -1. Callers then do
   `read_len += ret`, and `read_len` is `size_t`, so it becomes `SIZE_MAX`.
   `IOBufferReader::consume()` receives that as `int64_t` -1:
   
   ```c
   ink_release_assert(n == 0 || is_read_avail_more_than(n - 1));
   start_offset += n;
   ```
   
   `is_read_avail_more_than(-2)` is true, so the release-assert passes and
   `start_offset` moves *backwards* by one. The helper returns 0 rather than a
   negative, so `_on_encoder_stream_read_ready()` does not call 
`_abort_decode()`,
   and its `while (reader.is_read_avail_more_than(0))` loop reads the same byte
   again on the next iteration. `_read_duplicate()` (line 1578) has exactly this
   shape.
   
   Widening the destination types would address the truncation but not this 
half --
   the `< 0` check is what is missing.
   
   ### Relationship to #13621
   
   #13621 also touches `_read_insert_with_name_ref()` (line 1517), widening its
   `index` parameter to `uint64_t` so the narrowing disappears rather than being
   rejected. That is a better fix for that one site.
   
   This change flips the operator there too, so the ten sites are corrected
   consistently and none is left with a known-broken guard while #13621 is in
   review. If #13621 lands afterwards its approach supersedes the operator flip 
at
   that line -- a one-line resolution. Line 1524, the value guard in the same
   function, is not covered by #13621 at all.
   
   ### Test
   
   Adds `decode() rejects oversized Required Insert Count at entry` to
   `test_QPACK.cc`, tagged `[qpack-decode-entry-bounds]`. It drives
   `QPACK::decode()` with a 4-byte 8-bit-prefix varint encoding `0x10000` and
   asserts the decoder reports failure rather than accepting it.
   
   Confirmed to be a regression test: with the guards restored to `&&` it fails 
on
   both assertions with `sync_ret := 0` and
   `handler->last_event() := 2700` (`QPACK_EVENT_DECODE_COMPLETE`) -- the 
oversized
   count is accepted and truncated to 0. With the change it passes, 3 
assertions.
   
   Also run, 5/5 pass: `h3_proxy_verifier`, `h3_python_client`,
   `h3_stream_lifetime`, `h3_flow_control`, `h3_sni_check`.
   
   The remaining eight guards have no direct test. Reaching `_decode_header()`
   requires getting past the `QPACK::decode()` check this test exercises, and 
the
   encoder-stream helpers need a QPACK encoder stream driven with crafted
   instructions, which the current test harness does not set up.
   


-- 
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