Skip to content

Commit 8d894b0

Browse files
authored
http2: close connections when receiving too many headers (#1156)
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.
1 parent 28d6f4a commit 8d894b0

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

bfe_http2/frame.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,7 @@ func (fr *Framer) readMetaFrame(f *HeadersFrame) (*MetaHeadersFrame, error) {
15041504
hdec.SetEmitEnabled(false)
15051505
mh.Truncated = true
15061506
state.H2ErrMaxHeaderListSize.Inc(1)
1507+
remainSize = 0
15071508
return maxHeaderListSizeError{
15081509
streamID: f.FrameHeader.StreamID,
15091510
curHeaderListSize: fr.maxHeaderListSize() - remainSize + size,
@@ -1525,6 +1526,35 @@ func (fr *Framer) readMetaFrame(f *HeadersFrame) (*MetaHeadersFrame, error) {
15251526
var err error
15261527
for {
15271528
frag := hc.HeaderBlockFragment()
1529+
1530+
// Avoid parsing large amounts of headers that we will then discard.
1531+
// If the sender exceeds the max header list size by too much,
1532+
// skip parsing the fragment and close the connection.
1533+
//
1534+
// "Too much" is either any CONTINUATION frame after we've already
1535+
// exceeded the max header list size (in which case remainSize is 0),
1536+
// or a frame whose encoded size is more than twice the remaining
1537+
// header list bytes we're willing to accept.
1538+
if int64(len(frag)) > int64(2*remainSize) {
1539+
if VerboseLogs {
1540+
log.Printf("http2: header list too large")
1541+
}
1542+
// It would be nice to send a RST_STREAM before sending the GOAWAY,
1543+
// but the structure of the server's frame writer makes this difficult.
1544+
return nil, ConnectionError{ErrCodeProtocol, "http2: header list too large"}
1545+
}
1546+
1547+
// Also close the connection after any CONTINUATION frame following an
1548+
// invalid header, since we stop tracking the size of the headers after
1549+
// an invalid one.
1550+
if invalid != nil {
1551+
if VerboseLogs {
1552+
log.Printf("http2: invalid header: %v", invalid)
1553+
}
1554+
// It would be nice to send a RST_STREAM before sending the GOAWAY,
1555+
// but the structure of the server's frame writer makes this difficult.
1556+
return nil, ConnectionError{ErrCodeProtocol, fmt.Sprintf("http2: invalid header: %v", invalid)}
1557+
}
15281558
blockSize += len(frag)
15291559
if _, err = hdec.Write(frag); err != nil {
15301560
// do not return ConnectionError err type,

bfe_http2/frame_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ func TestMetaFrameHeader(t *testing.T) {
931931
maxHeaderListSize: (1 << 10) / 2,
932932
want: maxHeaderListSizeError{
933933
streamID: 1,
934-
curHeaderListSize: 536,
934+
curHeaderListSize: 550,
935935
maxHeaderListSize: 512,
936936
},
937937
},
@@ -1033,7 +1033,7 @@ func TestMetaFrameHeader(t *testing.T) {
10331033
}
10341034
return fmt.Sprintf("value %#v", v)
10351035
}
1036-
t.Errorf("%s:\n got: %v\nwant: %s", name, str(got), str(tt.want))
1036+
t.Errorf(" %s:\n got: %v\nwant: %s", name, str(got), str(tt.want))
10371037
}
10381038
if tt.wantErrReason != "" && tt.wantErrReason != fmt.Sprint(f.errDetail) {
10391039
t.Errorf("%s: got error reason %q; want %q", name, f.errDetail, tt.wantErrReason)

0 commit comments

Comments
 (0)