size hpack dynamic table for the 33-byte minimum entry - #3462
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens brpc’s HTTP/2 HPACK dynamic table implementation by sizing the dynamic-table header ring buffer for the RFC7541 minimum 33-byte entry, preventing a peer from triggering a fatal CHECK(!_header_queue.full()) via many small indexed headers.
Changes:
- Adjust
IndexTable::Initcapacity calculation from assuming 34-byte minimum entries to the correct 33-byte minimum. - Add a regression unit test that decodes 121 minimal (1-byte name, empty value) “literal with incremental indexing” headers to ensure decode does not abort.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/brpc/details/hpack.cpp |
Fixes dynamic-table queue sizing to avoid ring-buffer overflow before eviction for 33-byte minimum entries. |
test/brpc_hpack_unittest.cpp |
Adds regression coverage for decoding many minimal indexed headers without triggering fatal checks. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| _max_size = UINT_MAX; | ||
| } else { | ||
| num_headers = options.max_size / (32 + 2); | ||
| num_headers = options.max_size / (32 + 1); |
There was a problem hiding this comment.
Good catch. Pushed a guard that keeps num_headers at 1 when max_size is below 33 (including the max_size == 0 disable case), so we don't hit malloc(0). Nothing actually lands in that slot since entry_size > _max_size still holds in AddHeader. Added a test that Init(0) succeeds.
What problem does this PR solve?
Issue Number:
Problem Summary:
IndexTable::Initsizes the HPACK dynamic-table ring buffer asmax_size / (32 + 2), assuming every entry is at least 34 bytes. ButAddHeader()only requires a non-empty name, so a header with an empty value costsname(1) + value(0) + 32 = 33bytes (rfc7541 section 4.1). With the default 4096-byte table that leaves room for 124 such entries before eviction fires, while the queue only holds 120, so the 121stAddHeader()from a peer tripsCHECK(!_header_queue.full()). That is a fatal log plus stack trace on every crafted HEADERS block, and a process abort when-crash_on_fatal_logis set, all driven straight from HTTP/2 header bytes.What is changed and the side effects?
Changed:
Provision the queue for the real 33-byte minimum entry (
max_size / (32 + 1)) so eviction always fires before the ring buffer fills. Added a regression test that decodes 121 one-byte-name/empty-value indexed headers, which previously tripped the CHECK.Side effects:
Performance effects: negligible, a few more slots in the dynamic-table ring buffer.
Breaking backward compatibility: none.