From ca635f5ff755f8e65d4c83d52e86917fbbc62b99 Mon Sep 17 00:00:00 2001 From: Sagar Chanchal Date: Thu, 27 Aug 2026 18:41:29 +0530 Subject: [PATCH] Count HPACK header list entries with RFC 9113 per-entry overhead The header list byte limit was computed as name.size + value.size, so a literal header field whose name and value are both empty contributed nothing to the accounting. A peer could send CONTINUATION frames (which are not flow controlled) containing 3-byte zero-length literal headers and grow the header list without the limit ever being exceeded. Account for the 32-byte per-entry overhead defined by RFC 9113 section 6.5.2, matching nghttp2 and Jetty, so every header entry consumes budget and the limit cannot be bypassed with empty entries. Signed-off-by: Sagar Chanchal --- .../kotlin/okhttp3/internal/http2/Hpack.kt | 9 ++++++++- .../kotlin/okhttp3/internal/http2/HpackTest.kt | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Hpack.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Hpack.kt index 7c269d9c8da8..d759d44059a1 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Hpack.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Hpack.kt @@ -47,6 +47,11 @@ object Hpack { private const val PREFIX_6_BITS = 0x3f private const val PREFIX_7_BITS = 0x7f + /** + * Per-entry overhead of a header list entry, as defined by RFC 9113 section 6.5.2. + */ + private const val HEADER_ENTRY_OVERHEAD = 32 + private const val SETTINGS_HEADER_TABLE_SIZE = 4_096 /** @@ -411,7 +416,9 @@ object Hpack { private fun addHeader(header: Header) { headerList.add(header) - val headerSize = header.name.size + header.value.size + // Include the per-entry overhead of RFC 9113 section 6.5.2, so the accounting + // also grows for headers whose name and value are both empty. + val headerSize = header.name.size + header.value.size + HEADER_ENTRY_OVERHEAD val newHeaderListSize = headerListByteCount + headerSize headerListByteCount = newHeaderListSize if (newHeaderListSize > HEADER_LIMIT) { diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/internal/http2/HpackTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/internal/http2/HpackTest.kt index cf6fc58a1ea5..a6d1bd619810 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/internal/http2/HpackTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/internal/http2/HpackTest.kt @@ -36,6 +36,24 @@ class HpackTest { private val bytesOut = Buffer() private var hpackWriter: Hpack.Writer? = null + /** + * Each header list entry must account for the RFC 9113 section 6.5.2 per-entry overhead, + * including headers whose name and value are both empty. Otherwise a peer could flood + * zero-length literal header fields and grow the header list without ever exceeding + * [HEADER_LIMIT]. + */ + @Test + fun manyEmptyHeadersExceedByteLimit() { + repeat(8193) { + bytesIn.writeByte(0x00) // Literal Header Field without Indexing, new name. + bytesIn.writeByte(0x00) // Name length 0. + bytesIn.writeByte(0x00) // Value length 0. + } + assertFailsWith { + hpackReader!!.readHeaders() + } + } + @BeforeEach fun reset() { hpackReader = newReader(bytesIn)