JakeChampion / JakeChampion/trafficserver
[10.2][http3] QPACK static-table lookup has no bounds check — remote OOB read
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 0
- Forks
- 0
- Avg merge
- 8h 2m
- Merged PRs (30d)
- 21
Description
Branch: 10.2.x (10.2.1, commit 31f1f2f3b) · Severity: critical · ✓ adversarially verified
Location: src/proxy/http3/QPACK.cc:1222 (and encoder-insert caller at :1156)
What's wrong
QPACK::StaticTable::lookup(uint16_t index, ...) dereferences STATIC_HEADER_FIELDS[index] with no range check. STATIC_HEADER_FIELDS has only 99 entries, but index is taken from the wire (a QPACK integer up to 0xFFFF) on every HTTP/3 header decode that sets the static (T) bit — _decode_indexed_header_field and _decode_literal_header_field_with_name_ref, plus the encoder-stream Insert With Name Reference handler. A crafted HEADERS field with a static index ≥ 99 reads past the array; the garbage Header returned supplies a wild name/value pointer and length that flow into _attach_header → std::string_view{name, name_len}, giving an out-of-bounds read of attacker-influenced size (crash or header-memory disclosure).
Additionally the encoder-stream handler (:1156) calls StaticTable::lookup(index, ...) unconditionally, ignoring the is_static flag it just parsed, so dynamic-table name references also hit the unchecked static path.
Evidence
1220: QPACK::StaticTable::lookup(uint16_t index, const char **name, ...) {
1222: const Header &header = STATIC_HEADER_FIELDS[index]; // no bound check
...
1156: StaticTable::lookup(index, &name, &name_len, &dummy, &dummy_len); // is_static ignored
Fix
Bounds-check index against countof(STATIC_HEADER_FIELDS) and return MatchType::NONE (the callers already reject non-EXACT); honor is_static on the encoder insert by resolving dynamic references against the dynamic table, aborting the decode on an invalid index. PR attached.
Verification
Independently re-traced on the 10.2.1 tree by a second reviewer instructed to refute it; confirmed. STATIC_HEADER_FIELDS[] has exactly 99 entries; lookup() always returns EXACT with no range check, so the if (result.match_type != EXACT) return -1 guards never reject an out-of-range index; the index originates from an xpack_decode_integer bounded only to 16 bits, directly attacker-reachable via a crafted HTTP/3 field section.
From an automated multi-lens audit of the 10.2.x branch. Full report on branch claude/codebase-audit-review-9nw7vz (CODEBASE_AUDIT_10.2.md).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/proxy/http3/QPACK.cc at StaticTable::lookup around line 1222, then trace the callers around lines 1156 and 1222. Verify invalid static indexes are rejected and that encoder-stream references honor the parsed static flag. Done means malformed HTTP/3 inputs no longer permit the unchecked static lookup or out-of-bounds header read.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend-api-design, networking, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100