Skip to content

fix(qpack): reject post-base references at or above Required Insert Count - #380

Open
prostomarkeloff wants to merge 1 commit into
hyperium:masterfrom
prostomarkeloff:fix-qpack-postbase-ric
Open

prostomarkeloff wants to merge 1 commit into
hyperium:masterfrom
prostomarkeloff:fix-qpack-postbase-ric

Conversation

@prostomarkeloff

Copy link
Copy Markdown

Found with find-dup-defs using its converge pass, which reports behavioural divergences between near-duplicate functions — here the VirtualAddressSpace::relative / relative_base pair, which led to the sibling post_base next to them.

Problem

In h3/src/qpack/vas.rs, VirtualAddressSpace::post_base validates a post-base reference like this:

pub fn post_base(&self, base: usize, index: RelativeIndex) -> Result<usize, Error> {
    if self.delta == 0 || base + index >= self.inserted || base + index < self.dropped {
        Err(Error::PostbaseIndex(index))
    } else {
        Ok(base + index - self.dropped)
    }
}

Two issues, both reachable from Decoder::decode_header on a peer-supplied header block:

  1. base + index can overflow usize and panic. Base is Required Insert Count + Delta Base, and the post-base index is a prefix integer — both are read from the encoded block and can be ~2^63. Their sum overflows, which panics (attempt to add with overflow) in debug / with overflow-checks.

  2. The reference is bounded against the wrong quantity. It is compared to self.inserted (everything the dynamic table has ever inserted) rather than the block's declared Required Insert Count. RFC 9204 §4.5.1:

    If the decoder encounters a reference … to a dynamic table entry … that has an absolute index greater than or equal to the declared Required Insert Count … it MUST treat this as a connection error of type QPACK_DECOMPRESSION_FAILED.

    decode_header only checks required_ref <= total_inserted; the post-base path never compares against the Required Insert Count, so a block can reference an entry outside its declared window and still decode.

Impact: a crafted header block can panic the decoder (DoS on untrusted input) or decode a reference the RFC requires to be rejected.

Fix

Thread the block's Required Insert Count (required_ref) through DynamicTable::decoder(base, required_ref) into the address space, and in post_base:

match base.checked_add(index) {
    Some(abs) if self.delta != 0 && abs >= self.dropped && abs < required => Ok(abs - self.dropped),
    _ => Err(Error::PostbaseIndex(index)),
}

checked_add removes the overflow; the abs < required ceiling enforces §4.5.1. relative_base gains the same RFC bound (base - index > required → error). Absolute indices here are 1-based, so the 0-based abs < RIC of the RFC is base + index < required in these terms.

A test was encoding an RFC-invalid block

decode_post_base_name_ref_header_field declared Required Insert Count 2 while its post-base reference resolved to absolute index 3 — i.e. it referenced an entry beyond the count it declared, which §4.5.1 forbids. It only passed because of the missing check above. I raised its Required Insert Count to 4 (matching the sibling decode_post_base_indexed) with an explanatory comment, so it exercises a conformant block.

Tests

Two regression tests in qpack::decoder::tests: repro_post_base_overflow (overflowing post-base index → error, no panic) and repro_post_base_above_required_insert_count (absolute index ≥ RIC → error). cargo test -p h3 --lib qpack is green (135 passed).

Disclosure: found and prepared with the help of an AI-assisted tool (a duplicate/divergence analyzer plus an LLM). I have reviewed the change and the tests, and can explain them.

…ount

`VirtualAddressSpace::post_base` computed `base + index` without
`checked_add` — both are peer-controlled prefix integers (up to ~2^63),
so the sum can overflow and panic — and bounded the reference against the
table's total inserted count rather than the block's Required Insert
Count. RFC 9204 4.5.1 requires a reference whose absolute index is >= the
declared Required Insert Count to be treated as QPACK_DECOMPRESSION_FAILED.

Thread the Required Insert Count through DynamicTable::decoder into
post_base (checked_add plus an `abs < required` ceiling) and relative_base,
so an overflowing or out-of-window post-base index returns an error
instead of panicking or decoding an entry the RFC requires to be rejected.

Also fix decode_post_base_name_ref_header_field, which declared a Required
Insert Count (2) smaller than the absolute index its post-base reference
resolved to (3) — an RFC-invalid block that only decoded because of the
missing check.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant