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.
Hello,
While using
httlib-hpackas a reference implementation in a differential test of our own HPACK decoder, we found thatDecoder::decodepanics instead of returning an error on a large class of malformed inputs.Affected:
httlib-hpack0.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
Locations
Two sites confirmed, a third that looks reachable by the same class of input.
1.
hpack/src/decoder/primitives.rs:74, indecode_integer— reached by0x3f,0x0fand others: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 whilebuf[1]is out of bounds — the check and the index are not testing the same thing.2.
hpack/src/decoder/primitives.rs:108, indecode_string— reached by0x00,0x40and others:buf[0]is read before any emptiness check. When a preceding instruction says a string follows and the block ends there,bufis 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
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 inhttlib-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:
For site 2, an emptiness check before
buf[0]. For site 3, a checked subtraction, or comparing aslen + 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-hpackstill sees roughly 20 000 downloads per 90 days.Thanks for the work on this crate.