Skip to content

hpack: decoder panics on 97 of 256 single-byte inputs — missing bounds checks in decoder::primitives #14

Description

@Haaremy

Hello,

While using httlib-hpack as a reference implementation in a differential test of our own HPACK decoder, we found that Decoder::decode panics instead of returning an error on a large class of malformed inputs.

Affected: httlib-hpack 0.1.3.

Scope

We tested every one of the 256 possible single-byte inputs. 97 of them panic. For comparison, the same sweep against two other HPACK implementations gave 1 and 0 panics respectively.

Reproducers

fn dec(byte: u8) {
    let mut d = httlib_hpack::Decoder::default();
    let mut buf = vec![byte];
    let mut dst = Vec::new();
    let _ = d.decode(&mut buf, &mut dst);
}

fn main() {
    dec(0x00); // index out of bounds: the len is 0 but the index is 0
    dec(0x3f); // index out of bounds: the len is 1 but the index is 1
    dec(0x0f); // index out of bounds: the len is 1 but the index is 1
    dec(0x40); // index out of bounds: the len is 0 but the index is 0
}

Locations

Two sites confirmed, a third that looks reachable by the same class of input.

1. hpack/src/decoder/primitives.rs:74, in decode_integer — reached by 0x3f, 0x0f and others:

let byte = if buf.is_empty() {
    return Err(DecoderError::IntegerUnderflow);
} else {
    total += 1;
    buf[total - 1]
};

The guard tests whether the whole buffer is empty, but the index used is total - 1, which advances with each continuation octet. On the second iteration with a one-byte buffer, buf.is_empty() is false while buf[1] is out of bounds — the check and the index are not testing the same thing.

2. hpack/src/decoder/primitives.rs:108, in decode_string — reached by 0x00, 0x40 and others:

let huffman = buf[0] & 128 == 128;

buf[0] is read before any emptiness check. When a preceding instruction says a string follows and the block ends there, buf is empty at this point.

3. hpack/src/decoder/primitives.rs:113, same function — we did not isolate a triggering input for this one, so please treat it as a suspicion rather than a finding:

if len as usize > buf.len() - total {

If total > buf.len(), the subtraction underflows: a panic in debug builds, a wraparound to a very large value in release builds. The latter would turn the length check into a no-op.

What the bytes mean

  • 0x00 — literal without indexing, index 0, so a name string must follow; the block ends instead.
  • 0x40 — literal with incremental indexing, same shape.
  • 0x0f, 0x1f, 0x3f — literal / never-indexed / dynamic table size update with the prefix exhausted, so RFC 7541 §5.1 requires at least one continuation octet; none follows.

Impact

An HPACK decoder processes bytes that come entirely from the peer. Any connected peer whose complete header block reaches this decoder can trigger a panic. Whether that ends only a task or the whole process depends on how the crate is embedded and on the panic strategy, so we are not attaching a severity.

We looked for an existing public report covering these and did not find one; that does not establish that they are unreported. Issue #5 is related in spirit but is a different defect — it concerns Huffman padding acceptance in httlib-huffman, not bounds checking in httlib-hpack's primitives.

Suggested direction

All three share one shape: a length or index derived from already-consumed input is used without re-checking it against what remains. Rather than patching each site, it may be worth making that check explicit at the entry of each primitive.

For site 1, the guard needs to test the index actually used:

if total >= buf.len() {
    return Err(DecoderError::IntegerUnderflow);
}

For site 2, an emptiness check before buf[0]. For site 3, a checked subtraction, or comparing as len + total > buf.len() with overflow-safe arithmetic.

How we found it

A differential test between our own HPACK decoder and two independent implementations, across roughly 25 000 random and mutated inputs, plus an exhaustive sweep of all single- and two-byte inputs. Our repository is currently private; happy to share the relevant excerpt on request.

We note the last commit here is from October 2021. If the crate is no longer maintained, saying so would itself be useful information for downstream users — httlib-hpack still sees roughly 20 000 downloads per 90 days.

Thanks for the work on this crate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions