diff --git a/src/net/http/internal/http2/server.go b/src/net/http/internal/http2/server.go index addfc1dd24ffa3..463ebba48add0e 100644 --- a/src/net/http/internal/http2/server.go +++ b/src/net/http/internal/http2/server.go @@ -321,7 +321,19 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon if conf.CountError != nil { fr.countError = conf.CountError } - fr.ReadMetaHeaders = hpack.NewDecoder(uint32(conf.MaxDecoderHeaderTableSize), nil) + // A decoder table size below the initial 4096 (RFC 7540, Section 6.5.2) + // can't be applied immediately: the client may keep using the initial + // size until it processes our SETTINGS frame (RFC 7540, Section 6.5.3), + // and it signals the reduction with a dynamic table size update at the + // beginning of the first header block following the settings + // acknowledgment (RFC 7541, Section 4.2). Start at the initial size and + // lower it when the client acknowledges our SETTINGS. See processSettings. + decoderTableSize := uint32(conf.MaxDecoderHeaderTableSize) + if decoderTableSize < initialHeaderTableSize { + sc.pendingDecoderTableSize = decoderTableSize + decoderTableSize = initialHeaderTableSize + } + fr.ReadMetaHeaders = hpack.NewDecoder(decoderTableSize, nil) fr.MaxHeaderListSize = sc.maxHeaderListSize() fr.MaxHeaderValueCount = sc.hs.MaxHeaderValueCount() fr.SetMaxReadFrameSize(uint32(conf.MaxReadFrameSize)) @@ -443,6 +455,7 @@ type serverConn struct { sawFirstSettings bool // got the initial SETTINGS frame after the preface needToSendSettingsAck bool unackedSettings int // how many SETTINGS have we sent without ACKs? + pendingDecoderTableSize uint32 // if non-zero, HPACK decoder table size to apply on SETTINGS ack queuedControlFrames int // control frames in the writeSched queue clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit) advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client @@ -1614,6 +1627,17 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error { // hang up on them anyway. return sc.countError("ack_mystery", ConnectionError(ErrCodeProtocol)) } + if sc.pendingDecoderTableSize != 0 { + // The client has acknowledged our SETTINGS, so all header + // blocks it sends from now on were encoded with knowledge + // of our lower HEADER_TABLE_SIZE. It is now safe to apply + // the configured size to the decoder. The read goroutine + // is parked until readMore is called, so mutating the + // decoder here is race-free. + sc.framer.ReadMetaHeaders.SetAllowedMaxDynamicTableSize(sc.pendingDecoderTableSize) + sc.framer.ReadMetaHeaders.SetMaxDynamicTableSize(sc.pendingDecoderTableSize) + sc.pendingDecoderTableSize = 0 + } return nil } if f.NumSettings() > 100 || f.HasDuplicates() { diff --git a/src/net/http/internal/http2/server_test.go b/src/net/http/internal/http2/server_test.go index 727ab192ccf214..304badaa8f3500 100644 --- a/src/net/http/internal/http2/server_test.go +++ b/src/net/http/internal/http2/server_test.go @@ -2857,6 +2857,88 @@ func testServer_MaxDecoderHeaderTableSize(t *testing.T) { } } +func TestServer_MaxDecoderHeaderTableSize_DeferredUntilAck(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + testServer_MaxDecoderHeaderTableSizeDeferred(t, true) + }) +} + +func TestServer_MaxDecoderHeaderTableSize_EnforcedAfterAck(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + testServer_MaxDecoderHeaderTableSizeDeferred(t, false) + }) +} + +// testServer_MaxDecoderHeaderTableSizeDeferred tests that a server +// configured with a decoder header table size below the protocol-initial +// 4096 bytes does not apply it until the client acknowledges the server's +// SETTINGS frame. Until then, the client's encoder may legitimately +// reference dynamic table entries under the initial 4096-byte table size +// (RFC 7540, Section 6.5.3; RFC 7541, Section 4.2). +func testServer_MaxDecoderHeaderTableSizeDeferred(t *testing.T, sizeUpdateAfterAck bool) { + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(h2 *http.HTTP2Config) { + h2.MaxDecoderHeaderTableSize = 1 + }) + defer st.Close() + + st.writePreface() + st.writeSettings() + st.sync() + readFrame[*SettingsFrame](t, st) + + // The server's initial WINDOW_UPDATE and its ack of our SETTINGS + // can arrive in either order. + st.wantUnorderedFrames( + func(f *SettingsFrame) bool { + if !f.IsAck() { + t.Fatalf("got second non-ack SETTINGS frame") + } + return true + }, + func(f *WindowUpdateFrame) bool { + return true + }, + ) + + // Send two requests whose header blocks use the dynamic table, before + // acknowledging the server's SETTINGS. The second request references + // entries added by the first. Our encoder is still permitted to use + // the initial 4096-byte table size, so both must succeed. + for _, id := range []uint32{1, 3} { + st.writeHeaders(HeadersFrameParam{ + StreamID: id, + BlockFragment: st.encodeHeader("x-custom-header", "long-enough-value-to-index"), + EndStream: true, + EndHeaders: true, + }) + st.wantHeaders(wantHeader{streamID: id, endStream: true}) + } + + // Acknowledge the server's SETTINGS. Header blocks we send from here + // on must respect the reduced table size. + st.writeSettingsAck() + st.sync() + + if sizeUpdateAfterAck { + // A compliant encoder begins its next header block with a + // dynamic table size update. The request must succeed. + st.hpackEnc.SetMaxDynamicTableSize(1) + } + st.writeHeaders(HeadersFrameParam{ + StreamID: 5, + BlockFragment: st.encodeHeader("x-custom-header", "long-enough-value-to-index"), + EndStream: true, + EndHeaders: true, + }) + if sizeUpdateAfterAck { + st.wantHeaders(wantHeader{streamID: 5, endStream: true}) + } else { + // A header block that keeps referencing the now-evicted + // 4096-byte table without a size update must be rejected. + st.wantGoAway(5, ErrCodeCompression) + } +} + func TestServer_MaxEncoderHeaderTableSize(t *testing.T) { synctest.Test(t, testServer_MaxEncoderHeaderTableSize) }