Skip to content

Commit

Permalink
internal/transport: handle h2 errcode on header decoding (#3872)
Browse files Browse the repository at this point in the history
Handles HTTP2 error code when malformed request/response header appears.
Fixes: #3819
  • Loading branch information
tz70s committed Sep 17, 2020
1 parent 9ec6f11 commit bf9584e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 19 deletions.
4 changes: 2 additions & 2 deletions internal/transport/http2_client.go
Expand Up @@ -1206,8 +1206,8 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
state := &decodeState{}
// Initialize isGRPC value to be !initialHeader, since if a gRPC Response-Headers has already been received, then it means that the peer is speaking gRPC and we are in gRPC mode.
state.data.isGRPC = !initialHeader
if err := state.decodeHeader(frame); err != nil {
t.closeStream(s, err, true, http2.ErrCodeProtocol, status.Convert(err), nil, endStream)
if h2code, err := state.decodeHeader(frame); err != nil {
t.closeStream(s, err, true, h2code, status.Convert(err), nil, endStream)
return
}

Expand Down
6 changes: 3 additions & 3 deletions internal/transport/http2_server.go
Expand Up @@ -306,12 +306,12 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(
state := &decodeState{
serverSide: true,
}
if err := state.decodeHeader(frame); err != nil {
if se, ok := status.FromError(err); ok {
if h2code, err := state.decodeHeader(frame); err != nil {
if _, ok := status.FromError(err); ok {
t.controlBuf.put(&cleanupStream{
streamID: streamID,
rst: true,
rstCode: statusCodeConvTab[se.Code()],
rstCode: h2code,
onWrite: func() {},
})
}
Expand Down
21 changes: 7 additions & 14 deletions internal/transport/http_util.go
Expand Up @@ -73,13 +73,6 @@ var (
http2.ErrCodeInadequateSecurity: codes.PermissionDenied,
http2.ErrCodeHTTP11Required: codes.Internal,
}
statusCodeConvTab = map[codes.Code]http2.ErrCode{
codes.Internal: http2.ErrCodeInternal,
codes.Canceled: http2.ErrCodeCancel,
codes.Unavailable: http2.ErrCodeRefusedStream,
codes.ResourceExhausted: http2.ErrCodeEnhanceYourCalm,
codes.PermissionDenied: http2.ErrCodeInadequateSecurity,
}
// HTTPStatusConvTab is the HTTP status code to gRPC error code conversion table.
HTTPStatusConvTab = map[int]codes.Code{
// 400 Bad Request - INTERNAL.
Expand Down Expand Up @@ -222,11 +215,11 @@ func decodeMetadataHeader(k, v string) (string, error) {
return v, nil
}

func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) (http2.ErrCode, error) {
// frame.Truncated is set to true when framer detects that the current header
// list size hits MaxHeaderListSize limit.
if frame.Truncated {
return status.Error(codes.Internal, "peer header list size exceeded limit")
return http2.ErrCodeFrameSize, status.Error(codes.Internal, "peer header list size exceeded limit")
}

for _, hf := range frame.Fields {
Expand All @@ -235,10 +228,10 @@ func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {

if d.data.isGRPC {
if d.data.grpcErr != nil {
return d.data.grpcErr
return http2.ErrCodeProtocol, d.data.grpcErr
}
if d.serverSide {
return nil
return http2.ErrCodeNo, nil
}
if d.data.rawStatusCode == nil && d.data.statusGen == nil {
// gRPC status doesn't exist.
Expand All @@ -250,12 +243,12 @@ func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
code := int(codes.Unknown)
d.data.rawStatusCode = &code
}
return nil
return http2.ErrCodeNo, nil
}

// HTTP fallback mode
if d.data.httpErr != nil {
return d.data.httpErr
return http2.ErrCodeProtocol, d.data.httpErr
}

var (
Expand All @@ -270,7 +263,7 @@ func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
}
}

return status.Error(code, d.constructHTTPErrMsg())
return http2.ErrCodeProtocol, status.Error(code, d.constructHTTPErrMsg())
}

// constructErrMsg constructs error message to be returned in HTTP fallback mode.
Expand Down
65 changes: 65 additions & 0 deletions internal/transport/http_util_test.go
Expand Up @@ -23,6 +23,9 @@ import (
"reflect"
"testing"
"time"

"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
)

func (s) TestTimeoutDecode(t *testing.T) {
Expand Down Expand Up @@ -185,3 +188,65 @@ func (s) TestDecodeMetadataHeader(t *testing.T) {
}
}
}

func (s) TestDecodeHeaderH2ErrCode(t *testing.T) {
for _, test := range []struct {
name string
// input
metaHeaderFrame *http2.MetaHeadersFrame
serverSide bool
// output
wantCode http2.ErrCode
}{
{
name: "valid header",
metaHeaderFrame: &http2.MetaHeadersFrame{Fields: []hpack.HeaderField{
{Name: "content-type", Value: "application/grpc"},
}},
wantCode: http2.ErrCodeNo,
},
{
name: "valid header serverSide",
metaHeaderFrame: &http2.MetaHeadersFrame{Fields: []hpack.HeaderField{
{Name: "content-type", Value: "application/grpc"},
}},
serverSide: true,
wantCode: http2.ErrCodeNo,
},
{
name: "invalid grpc status header field",
metaHeaderFrame: &http2.MetaHeadersFrame{Fields: []hpack.HeaderField{
{Name: "content-type", Value: "application/grpc"},
{Name: "grpc-status", Value: "xxxx"},
}},
wantCode: http2.ErrCodeProtocol,
},
{
name: "invalid http content type",
metaHeaderFrame: &http2.MetaHeadersFrame{Fields: []hpack.HeaderField{
{Name: "content-type", Value: "application/json"},
}},
wantCode: http2.ErrCodeProtocol,
},
{
name: "http fallback and invalid http status",
metaHeaderFrame: &http2.MetaHeadersFrame{Fields: []hpack.HeaderField{
// No content type provided then fallback into handling http error.
{Name: ":status", Value: "xxxx"},
}},
wantCode: http2.ErrCodeProtocol,
},
{
name: "http2 frame size exceeds",
metaHeaderFrame: &http2.MetaHeadersFrame{Fields: nil, Truncated: true},
wantCode: http2.ErrCodeFrameSize,
},
} {
t.Run(test.name, func(t *testing.T) {
state := &decodeState{serverSide: test.serverSide}
if h2code, _ := state.decodeHeader(test.metaHeaderFrame); h2code != test.wantCode {
t.Fatalf("decodeState.decodeHeader(%v) = %v, want %v", test.metaHeaderFrame, h2code, test.wantCode)
}
})
}
}

0 comments on commit bf9584e

Please sign in to comment.