Skip to content

Commit b1cc14a

Browse files
committed
[release-branch.go1.11] 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. Updates 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]> (cherry picked from commit 589ad6cc5321fb68a90370348a241a5da0a2cc80) Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/526070 Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 48a9fcb commit b1cc14a

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
@@ -51,10 +51,11 @@ import (
5151
)
5252

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

6061
var (
@@ -162,6 +163,15 @@ func (s *Server) maxConcurrentStreams() uint32 {
162163
return defaultMaxStreams
163164
}
164165

166+
// maxQueuedControlFrames is the maximum number of control frames like
167+
// SETTINGS, PING and RST_STREAM that will be queued for writing before
168+
// the connection is closed to prevent memory exhaustion attacks.
169+
func (s *Server) maxQueuedControlFrames() int {
170+
// TODO: if anybody asks, add a Server field, and remember to define the
171+
// behavior of negative values.
172+
return maxQueuedControlFrames
173+
}
174+
165175
type serverInternalState struct {
166176
mu sync.Mutex
167177
activeConns map[*serverConn]struct{}
@@ -470,6 +480,7 @@ type serverConn struct {
470480
sawFirstSettings bool // got the initial SETTINGS frame after the preface
471481
needToSendSettingsAck bool
472482
unackedSettings int // how many SETTINGS have we sent without ACKs?
483+
queuedControlFrames int // control frames in the writeSched queue
473484
clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit)
474485
advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
475486
curClientStreams uint32 // number of open streams initiated by the client
@@ -857,6 +868,14 @@ func (sc *serverConn) serve() {
857868
}
858869
}
859870

871+
// If the peer is causing us to generate a lot of control frames,
872+
// but not reading them from us, assume they are trying to make us
873+
// run out of memory.
874+
if sc.queuedControlFrames > sc.srv.maxQueuedControlFrames() {
875+
sc.vlogf("http2: too many control frames in send queue, closing connection")
876+
return
877+
}
878+
860879
// Start the shutdown timer after sending a GOAWAY. When sending GOAWAY
861880
// with no error code (graceful shutdown), don't start the timer until
862881
// all open streams have been completed.
@@ -1056,6 +1075,14 @@ func (sc *serverConn) writeFrame(wr FrameWriteRequest) {
10561075
}
10571076

10581077
if !ignoreWrite {
1078+
if wr.isControl() {
1079+
sc.queuedControlFrames++
1080+
// For extra safety, detect wraparounds, which should not happen,
1081+
// and pull the plug.
1082+
if sc.queuedControlFrames < 0 {
1083+
sc.conn.Close()
1084+
}
1085+
}
10591086
sc.writeSched.Push(wr)
10601087
}
10611088
sc.scheduleFrameWrite()
@@ -1173,10 +1200,8 @@ func (sc *serverConn) wroteFrame(res frameWriteResult) {
11731200
// If a frame is already being written, nothing happens. This will be called again
11741201
// when the frame is done being written.
11751202
//
1176-
// If a frame isn't being written we need to send one, the best frame
1177-
// to send is selected, preferring first things that aren't
1178-
// stream-specific (e.g. ACKing settings), and then finding the
1179-
// highest priority stream.
1203+
// If a frame isn't being written and we need to send one, the best frame
1204+
// to send is selected by writeSched.
11801205
//
11811206
// If a frame isn't being written and there's nothing else to send, we
11821207
// flush the write buffer.
@@ -1204,6 +1229,9 @@ func (sc *serverConn) scheduleFrameWrite() {
12041229
}
12051230
if !sc.inGoAway || sc.goAwayCode == ErrCodeNo {
12061231
if wr, ok := sc.writeSched.Pop(); ok {
1232+
if wr.isControl() {
1233+
sc.queuedControlFrames--
1234+
}
12071235
sc.startFrameWrite(wr)
12081236
continue
12091237
}
@@ -1496,6 +1524,8 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error {
14961524
if err := f.ForeachSetting(sc.processSetting); err != nil {
14971525
return err
14981526
}
1527+
// TODO: judging by RFC 7540, Section 6.5.3 each SETTINGS frame should be
1528+
// acknowledged individually, even if multiple are received before the ACK.
14991529
sc.needToSendSettingsAck = true
15001530
sc.scheduleFrameWrite()
15011531
return nil

http2/server_test.go

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

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