Skip to content

Commit

Permalink
http2: close connections when receiving too many headers (#1156)
Browse files Browse the repository at this point in the history
Maintaining HPACK state requires that we parse and process all HEADERS and CONTINUATION frames on a connection. When a request's headers exceed MaxHeaderBytes, we don't allocate memory to store the excess headers but we do parse them. This permits an attacker to cause an HTTP/2 endpoint to read arbitrary amounts of header data, all associated with a request which is going to be rejected. These headers can include Huffman-encoded data which is significantly more expensive for the receiver to decode than for an attacker to send.

Set a limit on the amount of excess header frames we will process before closing a connection.

This is CVE-2023-45288 and Go issue https://go.dev/issue/65051.
  • Loading branch information
z8n24 authored May 16, 2024
1 parent 28d6f4a commit 8d894b0
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 2 deletions.
30 changes: 30 additions & 0 deletions bfe_http2/frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,7 @@ func (fr *Framer) readMetaFrame(f *HeadersFrame) (*MetaHeadersFrame, error) {
hdec.SetEmitEnabled(false)
mh.Truncated = true
state.H2ErrMaxHeaderListSize.Inc(1)
remainSize = 0
return maxHeaderListSizeError{
streamID: f.FrameHeader.StreamID,
curHeaderListSize: fr.maxHeaderListSize() - remainSize + size,
Expand All @@ -1525,6 +1526,35 @@ func (fr *Framer) readMetaFrame(f *HeadersFrame) (*MetaHeadersFrame, error) {
var err error
for {
frag := hc.HeaderBlockFragment()

// Avoid parsing large amounts of headers that we will then discard.
// If the sender exceeds the max header list size by too much,
// skip parsing the fragment and close the connection.
//
// "Too much" is either any CONTINUATION frame after we've already
// exceeded the max header list size (in which case remainSize is 0),
// or a frame whose encoded size is more than twice the remaining
// header list bytes we're willing to accept.
if int64(len(frag)) > int64(2*remainSize) {
if VerboseLogs {
log.Printf("http2: header list too large")
}
// It would be nice to send a RST_STREAM before sending the GOAWAY,
// but the structure of the server's frame writer makes this difficult.
return nil, ConnectionError{ErrCodeProtocol, "http2: header list too large"}
}

// Also close the connection after any CONTINUATION frame following an
// invalid header, since we stop tracking the size of the headers after
// an invalid one.
if invalid != nil {
if VerboseLogs {
log.Printf("http2: invalid header: %v", invalid)
}
// It would be nice to send a RST_STREAM before sending the GOAWAY,
// but the structure of the server's frame writer makes this difficult.
return nil, ConnectionError{ErrCodeProtocol, fmt.Sprintf("http2: invalid header: %v", invalid)}
}
blockSize += len(frag)
if _, err = hdec.Write(frag); err != nil {
// do not return ConnectionError err type,
Expand Down
4 changes: 2 additions & 2 deletions bfe_http2/frame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ func TestMetaFrameHeader(t *testing.T) {
maxHeaderListSize: (1 << 10) / 2,
want: maxHeaderListSizeError{
streamID: 1,
curHeaderListSize: 536,
curHeaderListSize: 550,
maxHeaderListSize: 512,
},
},
Expand Down Expand Up @@ -1033,7 +1033,7 @@ func TestMetaFrameHeader(t *testing.T) {
}
return fmt.Sprintf("value %#v", v)
}
t.Errorf("%s:\n got: %v\nwant: %s", name, str(got), str(tt.want))
t.Errorf(" %s:\n got: %v\nwant: %s", name, str(got), str(tt.want))
}
if tt.wantErrReason != "" && tt.wantErrReason != fmt.Sprint(f.errDetail) {
t.Errorf("%s: got error reason %q; want %q", name, f.errDetail, tt.wantErrReason)
Expand Down

0 comments on commit 8d894b0

Please sign in to comment.