Skip to content

Commit

Permalink
http2: close connections when receiving too many headers
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 data, all associated
with a request which is going to be rejected.

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

Thanks to Bartek Nowotarski for reporting this issue.

Fixes CVE-2023-45288
Fixes golang/go#65051

Change-Id: I15df097268df13bb5a9e9d3a5c04a8a141d850f6
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/2130527
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-on: https://go-review.googlesource.com/c/net/+/576155
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
  • Loading branch information
neild authored and gopherbot committed Apr 3, 2024
1 parent ebc8168 commit ba87210
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
31 changes: 31 additions & 0 deletions http2/frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
if size > remainSize {
hdec.SetEmitEnabled(false)
mh.Truncated = true
remainSize = 0
return
}
remainSize -= size
Expand All @@ -1576,6 +1577,36 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
var hc headersOrContinuation = hf
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 struture of the server's frame writer makes this difficult.
return nil, ConnectionError(ErrCodeProtocol)
}

// 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 struture of the server's frame writer makes this difficult.
return nil, ConnectionError(ErrCodeProtocol)
}

if _, err := hdec.Write(frag); err != nil {
return nil, ConnectionError(ErrCodeCompression)
}
Expand Down
84 changes: 84 additions & 0 deletions http2/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4786,3 +4786,87 @@ Frames:
close(s)
}
}

func TestServerContinuationFlood(t *testing.T) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.Header)
}, func(ts *httptest.Server) {
ts.Config.MaxHeaderBytes = 4096
})
defer st.Close()

st.writePreface()
st.writeInitialSettings()
st.writeSettingsAck()

st.writeHeaders(HeadersFrameParam{
StreamID: 1,
BlockFragment: st.encodeHeader(),
EndStream: true,
})
for i := 0; i < 1000; i++ {
st.fr.WriteContinuation(1, false, st.encodeHeaderRaw(
fmt.Sprintf("x-%v", i), "1234567890",
))
}
st.fr.WriteContinuation(1, true, st.encodeHeaderRaw(
"x-last-header", "1",
))

var sawGoAway bool
for {
f, err := st.readFrame()
if err != nil {
break
}
switch f.(type) {
case *GoAwayFrame:
sawGoAway = true
case *HeadersFrame:
t.Fatalf("received HEADERS frame; want GOAWAY")
}
}
if !sawGoAway {
t.Errorf("connection closed with no GOAWAY frame; want one")
}
}

func TestServerContinuationAfterInvalidHeader(t *testing.T) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.Header)
})
defer st.Close()

st.writePreface()
st.writeInitialSettings()
st.writeSettingsAck()

st.writeHeaders(HeadersFrameParam{
StreamID: 1,
BlockFragment: st.encodeHeader(),
EndStream: true,
})
st.fr.WriteContinuation(1, false, st.encodeHeaderRaw(
"x-invalid-header", "\x00",
))
st.fr.WriteContinuation(1, true, st.encodeHeaderRaw(
"x-valid-header", "1",
))

var sawGoAway bool
for {
f, err := st.readFrame()
if err != nil {
break
}
switch f.(type) {
case *GoAwayFrame:
sawGoAway = true
case *HeadersFrame:
t.Fatalf("received HEADERS frame; want GOAWAY")
}
}
if !sawGoAway {
t.Errorf("connection closed with no GOAWAY frame; want one")
}
}

0 comments on commit ba87210

Please sign in to comment.