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>
diff --git a/internal/http3/server.go b/internal/http3/server.go
index b1ca199..f7058f6 100644
--- a/internal/http3/server.go
+++ b/internal/http3/server.go
@@ -220,6 +220,9 @@
 	enc         qpackEncoder
 	dec         qpackDecoder
 
+	maxHeaderBytes      int64
+	maxHeaderValueCount int64
+
 	// For handling shutdown.
 	controlStream      *stream
 	mu                 sync.Mutex // Guards everything below.
@@ -231,11 +234,24 @@
 // The baseCtx parameter is the base context for request handlers on this connection.
 func (s *server) newServerConn(baseCtx context.Context, qconn *quic.Conn, h http.Handler) {
 	sc := &serverConn{
-		qconn:   qconn,
-		srv:     s,
-		baseCtx: baseCtx,
-		handler: h,
+		qconn:          qconn,
+		srv:            s,
+		baseCtx:        baseCtx,
+		handler:        h,
+		maxHeaderBytes: int64(s.srv1.MaxHeaderBytes),
+		// TODO: When we only support go1.27.
+		//maxHeaderValueCount: int64(s.srv1.MaxHeaderValueCount),
 	}
+
+	// Should we permit disabling these limits? For now, we do not.
+	if sc.maxHeaderBytes <= 0 {
+		sc.maxHeaderBytes = int64(http.DefaultMaxHeaderBytes)
+	}
+	// TODO: When we only support go1.27.
+	// if sc.maxHeaderValueCount <= 0 {
+	// 	sc.maxHeaderValueCount = int64(http.DefaultMaxHeaderValueCount)
+	// }
+
 	s.registerConn(sc)
 	defer s.unregisterConn(sc)
 	sc.enc.init()
@@ -247,7 +263,9 @@
 	if err != nil {
 		return
 	}
-	sc.controlStream.writeSettings()
+	sc.controlStream.writeSettings(
+		settingsMaxFieldSectionSize, sc.maxHeaderBytes,
+	)
 	sc.controlStream.Flush()
 
 	sc.acceptStreams(sc.qconn, sc)
@@ -362,11 +380,28 @@
 	if ftype != frameTypeHeaders {
 		return nil, pseudoHeader{}, &streamError{errH3MessageError, "received other frames when expecting HEADERS"}
 	}
+	if st.lim > sc.maxHeaderBytes {
+		// If the encoded headers exceed the limit, just reject the request out of hand.
+		// This lets us safely check limits in the dec.decode callback below,
+		// since the maximum Huffman expansion factor is only ~1.6x.
+		return nil, pseudoHeader{}, &streamError{errH3RequestRejected, "headers too large"}
+	}
 	header := make(http.Header)
+	valueCount := int64(0)
+	totalSize := int64(0)
 	var pHeader pseudoHeader
 	var dec qpackDecoder
 	var hasMethod, hasScheme, hasPath, hasAuthority bool
 	if err := dec.decode(st, func(_ indexType, name, value string) error {
+		totalSize += int64(len(name)) + int64(len(value)) + 32 // RFC 9114 Section 4.2.2
+		valueCount++
+		if totalSize > sc.maxHeaderBytes {
+			return &streamError{errH3RequestRejected, "headers too large"}
+		}
+		// TODO: When we only support go1.27.
+		//if valueCount > sc.maxHeaderValueCount {
+		//	return &streamError{errH3RequestRejected, "headers too large"}
+		//}
 		if !httpguts.ValidHeaderFieldValue(value) {
 			return &streamError{errH3MessageError, "invalid field value"}
 		}
diff --git a/internal/http3/server_test.go b/internal/http3/server_test.go
index e074cb0..0c2776a 100644
--- a/internal/http3/server_test.go
+++ b/internal/http3/server_test.go
@@ -16,6 +16,7 @@
 	"reflect"
 	"slices"
 	"strconv"
+	"strings"
 	"sync"
 	"testing"
 	"testing/synctest"
@@ -542,6 +543,72 @@
 	})
 }
 
+func TestServerHeaderLimits(t *testing.T) {
+	for _, test := range []struct {
+		name                string
+		h                   http.Header
+		valid               bool
+		maxHeaderBytes      int
+		maxHeaderValueCount int
+	}{{
+		name: "within limits",
+		h: http.Header{
+			"x-foo": {strings.Repeat("x", 1000)},
+		},
+		maxHeaderBytes: 1500,
+		valid:          true,
+	}, {
+		name: "too many header bytes",
+		h: http.Header{
+			"x-foo": {strings.Repeat("x", 1000)},
+			"x-bar": {strings.Repeat("x", 1000)},
+		},
+		maxHeaderBytes: 1500,
+	}, {
+		name: "field count within limit",
+		h: http.Header{
+			// :method, :scheme, :path, plus:
+			"x-foo": {"4"},
+			"x-bar": {"5"},
+		},
+		maxHeaderBytes:      1500,
+		maxHeaderValueCount: 5,
+		valid:               true,
+	}, {
+		name: "field count over limit",
+		h: http.Header{
+			// :method, :scheme, :path, plus:
+			"x-foo": {"4"},
+			"x-bar": {"5"},
+		},
+		maxHeaderBytes:      1500,
+		maxHeaderValueCount: 4,
+	}} {
+		synctestSubtest(t, test.name, func(t *testing.T) {
+			if test.maxHeaderValueCount != 0 {
+				t.Skip("TODO: when we support only go1.27")
+			}
+			ts := newTestServer(t, nil)
+			ts.s.srv1.MaxHeaderBytes = test.maxHeaderBytes
+			// TODO: When we only support go1.27.
+			//ts.s.srv1.MaxHeaderValueCount = test.maxHeaderValueCount
+			tc := ts.connect()
+			tc.greet()
+
+			reqStream := tc.newStream(streamTypeRequest)
+			reqStream.writeHeaders(requestHeader(test.h))
+			if test.valid {
+				call := tc.nextHandlerCall()
+				if call == nil {
+					t.Fatal("no server handler call; want one")
+				}
+			} else {
+				reqStream.wantError(quic.StreamErrorCode(errH3RequestRejected))
+			}
+		})
+	}
+}
+
 func TestServerBody(t *testing.T) {
 	synctest.Test(t, func(t *testing.T) {
 		ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {