Skip to content

Commit 12e0501

Browse files
committed
internal/http3: server header size limits
Apply Server.MaxHeaderBytes. Includes commented-out support for MaxHeaderValueCount. It's probably not worth using build tags to make this conditional on go 1.27, so just leave it out for the moment. Change-Id: I6452b6aba3336d48ebb877621ebecb7e6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/net/+/825264 Reviewed-by: Nicholas Husin <nsh@golang.org> Reviewed-by: Nicholas Husin <husin@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent d89285e commit 12e0501

2 files changed

Lines changed: 107 additions & 5 deletions

File tree

‎internal/http3/server.go‎

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ type serverConn struct {
220220
enc qpackEncoder
221221
dec qpackDecoder
222222

223+
maxHeaderBytes int64
224+
maxHeaderValueCount int64
225+
223226
// For handling shutdown.
224227
controlStream *stream
225228
mu sync.Mutex // Guards everything below.
@@ -231,11 +234,24 @@ type serverConn struct {
231234
// The baseCtx parameter is the base context for request handlers on this connection.
232235
func (s *server) newServerConn(baseCtx context.Context, qconn *quic.Conn, h http.Handler) {
233236
sc := &serverConn{
234-
qconn: qconn,
235-
srv: s,
236-
baseCtx: baseCtx,
237-
handler: h,
237+
qconn: qconn,
238+
srv: s,
239+
baseCtx: baseCtx,
240+
handler: h,
241+
maxHeaderBytes: int64(s.srv1.MaxHeaderBytes),
242+
// TODO: When we only support go1.27.
243+
//maxHeaderValueCount: int64(s.srv1.MaxHeaderValueCount),
244+
}
245+
246+
// Should we permit disabling these limits? For now, we do not.
247+
if sc.maxHeaderBytes <= 0 {
248+
sc.maxHeaderBytes = int64(http.DefaultMaxHeaderBytes)
238249
}
250+
// TODO: When we only support go1.27.
251+
// if sc.maxHeaderValueCount <= 0 {
252+
// sc.maxHeaderValueCount = int64(http.DefaultMaxHeaderValueCount)
253+
// }
254+
239255
s.registerConn(sc)
240256
defer s.unregisterConn(sc)
241257
sc.enc.init()
@@ -247,7 +263,9 @@ func (s *server) newServerConn(baseCtx context.Context, qconn *quic.Conn, h http
247263
if err != nil {
248264
return
249265
}
250-
sc.controlStream.writeSettings()
266+
sc.controlStream.writeSettings(
267+
settingsMaxFieldSectionSize, sc.maxHeaderBytes,
268+
)
251269
sc.controlStream.Flush()
252270

253271
sc.acceptStreams(sc.qconn, sc)
@@ -362,11 +380,28 @@ func (sc *serverConn) parseHeader(st *stream) (http.Header, pseudoHeader, error)
362380
if ftype != frameTypeHeaders {
363381
return nil, pseudoHeader{}, &streamError{errH3MessageError, "received other frames when expecting HEADERS"}
364382
}
383+
if st.lim > sc.maxHeaderBytes {
384+
// If the encoded headers exceed the limit, just reject the request out of hand.
385+
// This lets us safely check limits in the dec.decode callback below,
386+
// since the maximum Huffman expansion factor is only ~1.6x.
387+
return nil, pseudoHeader{}, &streamError{errH3RequestRejected, "headers too large"}
388+
}
365389
header := make(http.Header)
390+
valueCount := int64(0)
391+
totalSize := int64(0)
366392
var pHeader pseudoHeader
367393
var dec qpackDecoder
368394
var hasMethod, hasScheme, hasPath, hasAuthority bool
369395
if err := dec.decode(st, func(_ indexType, name, value string) error {
396+
totalSize += int64(len(name)) + int64(len(value)) + 32 // RFC 9114 Section 4.2.2
397+
valueCount++
398+
if totalSize > sc.maxHeaderBytes {
399+
return &streamError{errH3RequestRejected, "headers too large"}
400+
}
401+
// TODO: When we only support go1.27.
402+
//if valueCount > sc.maxHeaderValueCount {
403+
// return &streamError{errH3RequestRejected, "headers too large"}
404+
//}
370405
if !httpguts.ValidHeaderFieldValue(value) {
371406
return &streamError{errH3MessageError, "invalid field value"}
372407
}

‎internal/http3/server_test.go‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"reflect"
1717
"slices"
1818
"strconv"
19+
"strings"
1920
"sync"
2021
"testing"
2122
"testing/synctest"
@@ -542,6 +543,72 @@ func TestServerInvalidStatus(t *testing.T) {
542543
})
543544
}
544545

546+
func TestServerHeaderLimits(t *testing.T) {
547+
for _, test := range []struct {
548+
name string
549+
h http.Header
550+
valid bool
551+
maxHeaderBytes int
552+
maxHeaderValueCount int
553+
}{{
554+
name: "within limits",
555+
h: http.Header{
556+
"x-foo": {strings.Repeat("x", 1000)},
557+
},
558+
maxHeaderBytes: 1500,
559+
valid: true,
560+
}, {
561+
name: "too many header bytes",
562+
h: http.Header{
563+
"x-foo": {strings.Repeat("x", 1000)},
564+
"x-bar": {strings.Repeat("x", 1000)},
565+
},
566+
maxHeaderBytes: 1500,
567+
}, {
568+
name: "field count within limit",
569+
h: http.Header{
570+
// :method, :scheme, :path, plus:
571+
"x-foo": {"4"},
572+
"x-bar": {"5"},
573+
},
574+
maxHeaderBytes: 1500,
575+
maxHeaderValueCount: 5,
576+
valid: true,
577+
}, {
578+
name: "field count over limit",
579+
h: http.Header{
580+
// :method, :scheme, :path, plus:
581+
"x-foo": {"4"},
582+
"x-bar": {"5"},
583+
},
584+
maxHeaderBytes: 1500,
585+
maxHeaderValueCount: 4,
586+
}} {
587+
synctestSubtest(t, test.name, func(t *testing.T) {
588+
if test.maxHeaderValueCount != 0 {
589+
t.Skip("TODO: when we support only go1.27")
590+
}
591+
ts := newTestServer(t, nil)
592+
ts.s.srv1.MaxHeaderBytes = test.maxHeaderBytes
593+
// TODO: When we only support go1.27.
594+
//ts.s.srv1.MaxHeaderValueCount = test.maxHeaderValueCount
595+
tc := ts.connect()
596+
tc.greet()
597+
598+
reqStream := tc.newStream(streamTypeRequest)
599+
reqStream.writeHeaders(requestHeader(test.h))
600+
if test.valid {
601+
call := tc.nextHandlerCall()
602+
if call == nil {
603+
t.Fatal("no server handler call; want one")
604+
}
605+
} else {
606+
reqStream.wantError(quic.StreamErrorCode(errH3RequestRejected))
607+
}
608+
})
609+
}
610+
}
611+
545612
func TestServerBody(t *testing.T) {
546613
synctest.Test(t, func(t *testing.T) {
547614
ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)