Go 1.22.2 limits HTTP/2 work on rejected headers
CVE-2023-45288 let HTTP/2 peers consume decoding work after exceeding the accepted header size. Go 1.22.2 bounds excess processing by closing the connection.
An HTTP/2 endpoint could know that a request's headers were too large and still spend arbitrary amounts of work decoding them. Go already stopped retaining excess headers, but the compression state required continued parsing. An attacker could keep supplying header data for a request that would never reach useful application processing. Go 1.22.2, released on April 3, addresses that problem by bounding excess header processing and closing the connection when the bound is exceeded.
The CVE-2023-45288 disclosure describes a resource limit that covered stored results without covering all the work needed to decode them. I find this a useful example of why a protocol implementation needs several distinct resource budgets. A header-size setting cannot be assumed to bound every cost incurred before the handler receives a request.
Compression makes rejection stateful
HTTP/2's header compression uses HPACK. RFC 7541 describes its decoding context as a dynamic table. The encoder can add entries as header blocks are processed, and later fields can refer to entries by index. The decoder must apply those updates in order to interpret subsequent references correctly.
This creates a dependency between header blocks. Discarding an unwanted field does not necessarily mean its encoded representation can be skipped. A representation may update the dynamic table even when the application has no use for the resulting field. The receiver needs consistent compression state if it intends to continue using that connection.
The Go disclosure identifies HEADERS and CONTINUATION processing as the relevant path. Once headers exceeded MaxHeaderBytes, the implementation avoided allocating memory to store additional fields. It continued parsing the encoded data to maintain HPACK state. That protected one resource while leaving another available to the peer.
The attacker could supply Huffman-encoded data whose decoding cost exceeded the cost of sending it. The advisory does not require a useful or acceptable request at the end. Its central observation is that an endpoint continued reading and processing data associated with a request it was already going to reject.
This is different from a handler spending too long processing a valid request. The expensive operation takes place in protocol decoding. Application authorization or validation cannot reject material earlier than the parser that must first interpret it.
The fixed decoder budgets excess fragments
The Go 1.22.2 implementation of readMetaFrame shows how the repair enforces its boundary. The function starts with a remaining decoded-header budget. As it emits valid fields, it subtracts each field's size. When another field would exceed that budget, it disables field emission, marks the metadata truncated, and sets the remaining budget to zero.
Disabling emission alone is the behavior that needs an accompanying processing limit. The new check examines an encoded fragment before giving it to the HPACK decoder. If its byte length exceeds twice the remaining header-list budget, the function returns a connection-level protocol error. The code comments explain that this prevents parsing large quantities of headers that will then be discarded.
Once the remaining budget is zero, a subsequent nonempty fragment exceeds the check. The decoder therefore has a point at which it stops accepting further encoded input for the oversized header block. The relevant comparison is between encoded fragment length and remaining decoded-header allowance, so the implementation does not merely compare two totals representing the same thing.
The function also returns a connection error when another continuation follows an invalid header. Its comments explain that it stops tracking header size after detecting invalidity. Continuing to read under those conditions would lose the accounting needed to enforce the processing boundary.
These details matter because they identify what the patch actually guarantees. It limits excess processing using the decoder's remaining budget. It does not promise that every rejected header block costs zero work or that every encoded byte has the same decoding cost. Some work is necessary to determine that an input crosses the accepted boundary.
Closing the connection abandons its compression context
The fix chooses a connection error when the excess is too large. That choice follows from the state dependency. Continuing the connection requires maintaining the compression context. Abandoning the connection permits the endpoint to stop decoding input without preserving a state it intends to reuse.
There is a cost to this policy. A connection that carries offending input cannot continue carrying other work. The alternative exposed by this vulnerability is allowing a peer to impose unbounded processing so that the receiver can preserve the connection. The patch limits that obligation once the input exceeds the accepted allowance.
The implementation comments also distinguish a stream reset from a connection error. They note that sending a stream reset before the connection shutdown would be desirable, but that the server frame writer's structure makes it difficult. This is an implementation constraint visible in the released code, rather than an application setting that a handler can change.
I prefer this explicit failure boundary to relying on a header limit whose meaning excludes most of the expensive activity. The remaining-header budget now influences whether additional fragments are decoded at all. That places the decision in the component doing the work.
The standard library and module have separate update paths
The issue covers both net/http and golang.org/x/net/http2. A Go 1.22 backport record links the bundled HTTP/2 update used for Go 1.22.2. The main issue also records publication of golang.org/x/net version v0.23.0 with the fix.
Those are related delivery paths, but they are not interchangeable dependency declarations. A program using the standard library's bundled implementation receives that code through its Go toolchain. A program depending on the external module has a module version to consider as well. The issue's separate records make that distinction explicit without requiring an assumption about how a particular service assembles its networking stack.
The operational effect depends on which component decodes HTTP/2 input. A service handler may sit behind another endpoint that terminates the protocol. The disclosed work occurs at the decoder, so the relevant component is the one accepting and decompressing the header blocks. Merely identifying the language of the final handler does not identify that component.
The patch preserves normal header decoding while limiting the amount of discarded input a peer can make it process. HPACK's ordered state explains why the old implementation kept reading. The connection-level error supplies a bounded way to stop when maintaining that state would require excessive work on already unacceptable headers.