Skip to content

Commit 74dc4d7

Browse files
committed
http2: limit number of control frames in server send queue
An attacker could cause servers to queue an unlimited number of PING ACKs or RST_STREAM frames by soliciting them and not reading them, until the program runs out of memory. Limit control frames in the queue to a few thousands (matching the limit imposed by other vendors) by counting as they enter and exit the scheduler, so the protection will work with any WriteScheduler. Once the limit is exceeded, close the connection, as we have no way to communicate with the peer. This addresses CVE-2019-9512 and CVE-2019-9514. Fixes golang/go#33606 Change-Id: I842968fc6ed3eac654b497ade8cea86f7267886b Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/525552 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent ca1201d commit 74dc4d7

File tree

3 files changed

+71
-9
lines changed

3 files changed

+71
-9
lines changed

http2/server.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ import (
5252
)
5353

5454
const (
55-
prefaceTimeout = 10 * time.Second
56-
firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
57-
handlerChunkWriteSize = 4 << 10
58-
defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
55+
prefaceTimeout = 10 * time.Second
56+
firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
57+
handlerChunkWriteSize = 4 << 10
58+
defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
59+
maxQueuedControlFrames = 10000
5960
)
6061

6162
var (
@@ -163,6 +164,15 @@ func (s *Server) maxConcurrentStreams() uint32 {
163164
return defaultMaxStreams
164165
}
165166

167+
// maxQueuedControlFrames is the maximum number of control frames like
168+
// SETTINGS, PING and RST_STREAM that will be queued for writing before
169+
// the connection is closed to prevent memory exhaustion attacks.
170+
func (s *Server) maxQueuedControlFrames() int {
171+
// TODO: if anybody asks, add a Server field, and remember to define the
172+
// behavior of negative values.
173+
return maxQueuedControlFrames
174+
}
175+
166176
type serverInternalState struct {
167177
mu sync.Mutex
168178
activeConns map[*serverConn]struct{}
@@ -506,6 +516,7 @@ type serverConn struct {
506516
sawFirstSettings bool // got the initial SETTINGS frame after the preface
507517
needToSendSettingsAck bool
508518
unackedSettings int // how many SETTINGS have we sent without ACKs?
519+
queuedControlFrames int // control frames in the writeSched queue
509520
clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit)
510521
advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
511522
curClientStreams uint32 // number of open streams initiated by the client
@@ -894,6 +905,14 @@ func (sc *serverConn) serve() {
894905
}
895906
}
896907

908+
// If the peer is causing us to generate a lot of control frames,
909+
// but not reading them from us, assume they are trying to make us
910+
// run out of memory.
911+
if sc.queuedControlFrames > sc.srv.maxQueuedControlFrames() {
912+
sc.vlogf("http2: too many control frames in send queue, closing connection")
913+
return
914+
}
915+
897916
// Start the shutdown timer after sending a GOAWAY. When sending GOAWAY
898917
// with no error code (graceful shutdown), don't start the timer until
899918
// all open streams have been completed.
@@ -1093,6 +1112,14 @@ func (sc *serverConn) writeFrame(wr FrameWriteRequest) {
10931112
}
10941113

10951114
if !ignoreWrite {
1115+
if wr.isControl() {
1116+
sc.queuedControlFrames++
1117+
// For extra safety, detect wraparounds, which should not happen,
1118+
// and pull the plug.
1119+
if sc.queuedControlFrames < 0 {
1120+
sc.conn.Close()
1121+
}
1122+
}
10961123
sc.writeSched.Push(wr)
10971124
}
10981125
sc.scheduleFrameWrite()
@@ -1210,10 +1237,8 @@ func (sc *serverConn) wroteFrame(res frameWriteResult) {
12101237
// If a frame is already being written, nothing happens. This will be called again
12111238
// when the frame is done being written.
12121239
//
1213-
// If a frame isn't being written we need to send one, the best frame
1214-
// to send is selected, preferring first things that aren't
1215-
// stream-specific (e.g. ACKing settings), and then finding the
1216-
// highest priority stream.
1240+
// If a frame isn't being written and we need to send one, the best frame
1241+
// to send is selected by writeSched.
12171242
//
12181243
// If a frame isn't being written and there's nothing else to send, we
12191244
// flush the write buffer.
@@ -1241,6 +1266,9 @@ func (sc *serverConn) scheduleFrameWrite() {
12411266
}
12421267
if !sc.inGoAway || sc.goAwayCode == ErrCodeNo {
12431268
if wr, ok := sc.writeSched.Pop(); ok {
1269+
if wr.isControl() {
1270+
sc.queuedControlFrames--
1271+
}
12441272
sc.startFrameWrite(wr)
12451273
continue
12461274
}
@@ -1533,6 +1561,8 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error {
15331561
if err := f.ForeachSetting(sc.processSetting); err != nil {
15341562
return err
15351563
}
1564+
// TODO: judging by RFC 7540, Section 6.5.3 each SETTINGS frame should be
1565+
// acknowledged individually, even if multiple are received before the ACK.
15361566
sc.needToSendSettingsAck = true
15371567
sc.scheduleFrameWrite()
15381568
return nil

http2/server_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,32 @@ func TestServer_Ping(t *testing.T) {
11601160
}
11611161
}
11621162

1163+
func TestServer_MaxQueuedControlFrames(t *testing.T) {
1164+
if testing.Short() {
1165+
t.Skip("skipping in short mode")
1166+
}
1167+
1168+
st := newServerTester(t, nil)
1169+
defer st.Close()
1170+
st.greet()
1171+
1172+
const extraPings = 500000 // enough to fill the TCP buffers
1173+
1174+
for i := 0; i < maxQueuedControlFrames+extraPings; i++ {
1175+
pingData := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
1176+
if err := st.fr.WritePing(false, pingData); err != nil {
1177+
if i == 0 {
1178+
t.Fatal(err)
1179+
}
1180+
// We expect the connection to get closed by the server when the TCP
1181+
// buffer fills up and the write queue reaches MaxQueuedControlFrames.
1182+
t.Logf("sent %d PING frames", i)
1183+
return
1184+
}
1185+
}
1186+
t.Errorf("unexpected success sending all PING frames")
1187+
}
1188+
11631189
func TestServer_RejectsLargeFrames(t *testing.T) {
11641190
if runtime.GOOS == "windows" {
11651191
t.Skip("see golang.org/issue/13434")

http2/writesched.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type WriteScheduler interface {
3232

3333
// Pop dequeues the next frame to write. Returns false if no frames can
3434
// be written. Frames with a given wr.StreamID() are Pop'd in the same
35-
// order they are Push'd.
35+
// order they are Push'd. No frames should be discarded except by CloseStream.
3636
Pop() (wr FrameWriteRequest, ok bool)
3737
}
3838

@@ -76,6 +76,12 @@ func (wr FrameWriteRequest) StreamID() uint32 {
7676
return wr.stream.id
7777
}
7878

79+
// isControl reports whether wr is a control frame for MaxQueuedControlFrames
80+
// purposes. That includes non-stream frames and RST_STREAM frames.
81+
func (wr FrameWriteRequest) isControl() bool {
82+
return wr.stream == nil
83+
}
84+
7985
// DataSize returns the number of flow control bytes that must be consumed
8086
// to write this entire frame. This is 0 for non-DATA frames.
8187
func (wr FrameWriteRequest) DataSize() int {

0 commit comments

Comments
 (0)