Nginx Leaves Dynamic Tables Unused in HTTP/2 and HTTP/3 Responses

Nginx implements one-way header compression for both HTTP/2 and HTTP/3: clients compress repeated headers using HPACK or QPACK dynamic tables, but the server never writes to its own dynamic table when sending responses. Code analysis of release-1.31.3 shows every call to ngx_http_v3_encode_field_ri and ngx_http_v3_encode_field_lri passes dynamic=0, and the server encoder stream (NGX_HTTP_V3_STREAM_SERVER_ENCODER) is never created. The same pattern exists in the HPACK filter module, which explicitly sends a table-size update of zero and never calls ngx_http_v2_hpack_t structures. The receiving side fully supports dynamic tables up to the default 4096-byte limit and correctly processes insertions from clients such as aioquic. This asymmetry is permitted by RFC 9204 §4.2 yet removes the compression benefit the binary protocols were designed to provide. The unused encoder path contributed to a use-after-free vulnerability (CVSS 9.2) discovered earlier this year in the QPACK decoder.

HabrTranslated from Russian

Read at source

Nginx applies header compression asymmetrically in both HTTP/2 and HTTP/3. While clients compress repeated headers after the first transmission, the server always emits full literals, keeping response header sizes constant regardless of repetition count.

Measurements on a minimal stand with four identical requests show server responses locked at 131 bytes under HTTP/3 and 129 bytes under HTTP/2. Client requests, by contrast, drop from 246 bytes to 8 bytes once the dynamic table is populated.

Inspection of release-1.31.3 reveals the root cause inside ngx_http_v3_filter_module.c: all twenty-two invocations of ngx_http_v3_encode_field_ri and ngx_http_v3_encode_field_lri pass a zero value for the dynamic flag, forcing every field to be encoded as a literal. The field-section prefix is likewise emitted with insert_count and delta_base set to zero. No server-side encoder stream of type 0x02 is ever opened.

The identical limitation exists in the HPACK implementation. The HTTP/2 filter module contains zero references to ngx_http_v2_hpack_t and explicitly sends a Dynamic Table Size Update of zero on the first HEADERS frame, disabling client-side table usage for server responses.

The receiving path, however, is fully functional. ngx_http_v3_table.c implements insertion, eviction, blocked-stream tracking and the default 4096-byte table capacity announced in the SETTINGS frame. Debug logs confirm that client-supplied headers are correctly inserted and later referenced by index.

The asymmetry is explicitly allowed by RFC 9204 §4.2, yet it eliminates the compression gains that motivated the introduction of QPACK and HPACK. The unused encoder code path also contributed to a use-after-free vulnerability (CVSS 9.2) in the QPACK decoder earlier this year.

Meridian digestThe week's most consequential security stories, distilled into one briefing.See the digest

More in Vulnerabilities & Exploits

Telegram for Android Patches Voice Call Vulnerability Enabling Code Execution
Yandex Patches Millions of Stations After eMudhra Certificate Keys Compromised
Cisco ASA and FTD Face DoS Vulnerability in SSL VPN Already Exploited in the Wild