Skip to content

Commit 23406a6

Browse files
committed
Check header size
1 parent 87c4d96 commit 23406a6

4 files changed

Lines changed: 115 additions & 44 deletions

File tree

internal/transport/controlbuf.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,12 @@ type cleanupStream struct {
147147
func (c *cleanupStream) isTransportResponseFrame() bool { return c.rst } // Results in a RST_STREAM
148148

149149
type earlyAbortStream struct {
150-
httpStatus uint32
151-
streamID uint32
152-
contentSubtype string
153-
status *status.Status
154-
rst bool
150+
httpStatus uint32
151+
streamID uint32
152+
contentSubtype string
153+
status *status.Status
154+
rst bool
155+
maxSendHeaderListSize *uint32
155156
}
156157

157158
func (*earlyAbortStream) isTransportResponseFrame() bool { return false }
@@ -854,6 +855,13 @@ func (l *loopyWriter) earlyAbortStreamHandler(eas *earlyAbortStream) error {
854855
{Name: "grpc-message", Value: encodeGrpcMessage(eas.status.Message())},
855856
}
856857

858+
if !checkForHeaderListSize(headerFields, eas.maxSendHeaderListSize) {
859+
if l.logger.V(logLevel) {
860+
l.logger.Infof("Header list size to send violates the maximum size (%d bytes) set by client", *eas.maxSendHeaderListSize)
861+
}
862+
return l.framer.fr.WriteRSTStream(eas.streamID, http2.ErrCodeInternal)
863+
}
864+
857865
if err := l.writeHeader(eas.streamID, true, headerFields, nil); err != nil {
858866
return err
859867
}

internal/transport/http2_server.go

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -480,11 +480,12 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
480480
t.logger.Infof("Aborting the stream early: %v", errMsg)
481481
}
482482
t.controlBuf.put(&earlyAbortStream{
483-
httpStatus: http.StatusBadRequest,
484-
streamID: streamID,
485-
contentSubtype: s.contentSubtype,
486-
status: status.New(codes.Internal, errMsg),
487-
rst: !frame.StreamEnded(),
483+
httpStatus: http.StatusBadRequest,
484+
streamID: streamID,
485+
contentSubtype: s.contentSubtype,
486+
status: status.New(codes.Internal, errMsg),
487+
rst: !frame.StreamEnded(),
488+
maxSendHeaderListSize: t.maxSendHeaderListSize,
488489
})
489490
return nil
490491
}
@@ -500,21 +501,23 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
500501
}
501502
if !isGRPC {
502503
t.controlBuf.put(&earlyAbortStream{
503-
httpStatus: http.StatusUnsupportedMediaType,
504-
streamID: streamID,
505-
contentSubtype: s.contentSubtype,
506-
status: status.Newf(codes.InvalidArgument, "invalid gRPC request content-type %q", contentType),
507-
rst: !frame.StreamEnded(),
504+
httpStatus: http.StatusUnsupportedMediaType,
505+
streamID: streamID,
506+
contentSubtype: s.contentSubtype,
507+
status: status.Newf(codes.InvalidArgument, "invalid gRPC request content-type %q", contentType),
508+
rst: !frame.StreamEnded(),
509+
maxSendHeaderListSize: t.maxSendHeaderListSize,
508510
})
509511
return nil
510512
}
511513
if headerError != nil {
512514
t.controlBuf.put(&earlyAbortStream{
513-
httpStatus: http.StatusBadRequest,
514-
streamID: streamID,
515-
contentSubtype: s.contentSubtype,
516-
status: headerError,
517-
rst: !frame.StreamEnded(),
515+
httpStatus: http.StatusBadRequest,
516+
streamID: streamID,
517+
contentSubtype: s.contentSubtype,
518+
status: headerError,
519+
rst: !frame.StreamEnded(),
520+
maxSendHeaderListSize: t.maxSendHeaderListSize,
518521
})
519522
return nil
520523
}
@@ -570,11 +573,12 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
570573
t.logger.Infof("Aborting the stream early: %v", errMsg)
571574
}
572575
t.controlBuf.put(&earlyAbortStream{
573-
httpStatus: http.StatusMethodNotAllowed,
574-
streamID: streamID,
575-
contentSubtype: s.contentSubtype,
576-
status: status.New(codes.Internal, errMsg),
577-
rst: !frame.StreamEnded(),
576+
httpStatus: http.StatusMethodNotAllowed,
577+
streamID: streamID,
578+
contentSubtype: s.contentSubtype,
579+
status: status.New(codes.Internal, errMsg),
580+
rst: !frame.StreamEnded(),
581+
maxSendHeaderListSize: t.maxSendHeaderListSize,
578582
})
579583
s.cancel()
580584
return nil
@@ -591,11 +595,12 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
591595
stat = status.New(codes.PermissionDenied, err.Error())
592596
}
593597
t.controlBuf.put(&earlyAbortStream{
594-
httpStatus: http.StatusOK,
595-
streamID: s.id,
596-
contentSubtype: s.contentSubtype,
597-
status: stat,
598-
rst: !frame.StreamEnded(),
598+
httpStatus: http.StatusOK,
599+
streamID: s.id,
600+
contentSubtype: s.contentSubtype,
601+
status: stat,
602+
rst: !frame.StreamEnded(),
603+
maxSendHeaderListSize: t.maxSendHeaderListSize,
599604
})
600605
return nil
601606
}
@@ -605,11 +610,12 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
605610
t.mu.Unlock()
606611
// Early abort in case the timeout was zero or so low it already fired.
607612
t.controlBuf.put(&earlyAbortStream{
608-
httpStatus: http.StatusOK,
609-
streamID: s.id,
610-
contentSubtype: s.contentSubtype,
611-
status: status.New(codes.DeadlineExceeded, context.DeadlineExceeded.Error()),
612-
rst: !frame.StreamEnded(),
613+
httpStatus: http.StatusOK,
614+
streamID: s.id,
615+
contentSubtype: s.contentSubtype,
616+
status: status.New(codes.DeadlineExceeded, context.DeadlineExceeded.Error()),
617+
rst: !frame.StreamEnded(),
618+
maxSendHeaderListSize: t.maxSendHeaderListSize,
613619
})
614620
return nil
615621
}
@@ -969,23 +975,32 @@ func appendHeaderFieldsFromMD(headerFields []hpack.HeaderField, md metadata.MD)
969975
return headerFields
970976
}
971977

972-
func (t *http2Server) checkForHeaderListSize(it any) bool {
973-
if t.maxSendHeaderListSize == nil {
978+
// checkForHeaderListSize checks if the header list size exceeds the limit set
979+
// by the peer. It returns false if the limit is exceeded.
980+
func checkForHeaderListSize(hf []hpack.HeaderField, maxSendHeaderListSize *uint32) bool {
981+
if maxSendHeaderListSize == nil {
974982
return true
975983
}
976-
hdrFrame := it.(*headerFrame)
977984
var sz int64
978-
for _, f := range hdrFrame.hf {
979-
if sz += int64(f.Size()); sz > int64(*t.maxSendHeaderListSize) {
980-
if t.logger.V(logLevel) {
981-
t.logger.Infof("Header list size to send violates the maximum size (%d bytes) set by client", *t.maxSendHeaderListSize)
982-
}
985+
for _, f := range hf {
986+
if sz += int64(f.Size()); sz > int64(*maxSendHeaderListSize) {
983987
return false
984988
}
985989
}
986990
return true
987991
}
988992

993+
func (t *http2Server) checkForHeaderListSize(it any) bool {
994+
hdrFrame := it.(*headerFrame)
995+
if !checkForHeaderListSize(hdrFrame.hf, t.maxSendHeaderListSize) {
996+
if t.logger.V(logLevel) {
997+
t.logger.Infof("Header list size to send violates the maximum size (%d bytes) set by client", *t.maxSendHeaderListSize)
998+
}
999+
return false
1000+
}
1001+
return true
1002+
}
1003+
9891004
func (t *http2Server) streamContextErr(s *ServerStream) error {
9901005
select {
9911006
case <-t.done:

test/end2end_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6090,6 +6090,43 @@ func testClientMaxHeaderListSizeServerIntentionalViolation(t *testing.T, e env)
60906090
}
60916091
}
60926092

6093+
func (s) TestEarlyAbortStreamHeaderListSizeCheck(t *testing.T) {
6094+
lis, err := net.Listen("tcp", "localhost:0")
6095+
if err != nil {
6096+
t.Fatalf("Failed to listen: %v", err)
6097+
}
6098+
s := grpc.NewServer()
6099+
defer s.Stop()
6100+
go s.Serve(lis)
6101+
6102+
conn, err := net.DialTimeout("tcp", lis.Addr().String(), defaultTestTimeout)
6103+
if err != nil {
6104+
t.Fatalf("Failed to dial: %v", err)
6105+
}
6106+
defer conn.Close()
6107+
st := newServerTesterFromConn(t, conn)
6108+
6109+
// Set a very small MaxHeaderListSize that any response headers would violate.
6110+
st.greetWithSettings(http2.Setting{ID: http2.SettingMaxHeaderListSize, Val: 1})
6111+
6112+
// Send a request with an invalid content-type to trigger early abort.
6113+
st.writeHeaders(http2.HeadersFrameParam{
6114+
StreamID: 1,
6115+
BlockFragment: st.encodeHeader(
6116+
":method", "POST",
6117+
":path", "/grpc.testing.TestService/UnaryCall",
6118+
"content-type", "text/plain", // Invalid content-type to trigger early abort
6119+
"te", "trailers",
6120+
),
6121+
EndStream: true,
6122+
EndHeaders: true,
6123+
})
6124+
6125+
// We should receive a RST_STREAM with ErrCodeInternal because the response
6126+
// headers exceed the MaxHeaderListSize limit.
6127+
st.wantRSTStream(http2.ErrCodeInternal)
6128+
}
6129+
60936130
func (s) TestNetPipeConn(t *testing.T) {
60946131
// This test will block indefinitely if grpc writes both client and server
60956132
// prefaces without either reading from the Conn.

test/servertester.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,19 @@ func (st *serverTester) readFrame() (http2.Frame, error) {
9191
// greet initiates the client's HTTP/2 connection into a state where
9292
// frames may be sent.
9393
func (st *serverTester) greet() {
94+
st.greetWithSettings()
95+
}
96+
97+
// greetWithSettings initiates the client's HTTP/2 connection with custom settings.
98+
func (st *serverTester) greetWithSettings(settings ...http2.Setting) {
9499
st.writePreface()
95-
st.writeInitialSettings()
100+
if len(settings) > 0 {
101+
if err := st.fr.WriteSettings(settings...); err != nil {
102+
st.t.Fatalf("Error writing initial SETTINGS frame from client to server: %v", err)
103+
}
104+
} else {
105+
st.writeInitialSettings()
106+
}
96107
st.wantSettings()
97108
st.writeSettingsAck()
98109
for {

0 commit comments

Comments
 (0)