Commit b2ed34f6 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

http2: validate received header field values in Server and Transport

This validates incoming header field values in Server and Transport to
make sure the peer isn't sending us a \x00, CR, NL or other non-VCHAR
except space and tab.

It does not yet validate that we don't send such things, though.

Updates golang/go#14029

Change-Id: I7c6a56d5d0d255f1b8fa64480b34b3b5e1f4f367
Reviewed-on: https://go-review.googlesource.com/18727Reviewed-by: 's avatarAndrew Gerrand <adg@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 78e1654e
...@@ -17,6 +17,7 @@ package http2 ...@@ -17,6 +17,7 @@ package http2
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
...@@ -158,17 +159,60 @@ func (s SettingID) String() string { ...@@ -158,17 +159,60 @@ func (s SettingID) String() string {
return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s)) return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s))
} }
func validHeader(v string) bool { var (
errInvalidHeaderFieldName = errors.New("http2: invalid header field name")
errInvalidHeaderFieldValue = errors.New("http2: invalid header field value")
)
// validHeaderFieldName reports whether v is a valid header field name (key).
// RFC 7230 says:
// header-field = field-name ":" OWS field-value OWS
// field-name = token
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
// "^" / "_" / "
// Further, http2 says:
// "Just as in HTTP/1.x, header field names are strings of ASCII
// characters that are compared in a case-insensitive
// fashion. However, header field names MUST be converted to
// lowercase prior to their encoding in HTTP/2. "
func validHeaderFieldName(v string) bool {
if len(v) == 0 { if len(v) == 0 {
return false return false
} }
for _, r := range v { for _, r := range v {
// "Just as in HTTP/1.x, header field names are if int(r) >= len(isTokenTable) || ('A' <= r && r <= 'Z') {
// strings of ASCII characters that are compared in a return false
// case-insensitive fashion. However, header field }
// names MUST be converted to lowercase prior to their if !isTokenTable[byte(r)] {
// encoding in HTTP/2. " return false
if r >= 127 || ('A' <= r && r <= 'Z') { }
}
return true
}
// validHeaderFieldValue reports whether v is a valid header field value.
//
// RFC 7230 says:
// field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
// field-vchar = VCHAR / obs-text
// obs-text = %x80-FF
// VCHAR = "any visible [USASCII] character"
//
// http2 further says: "Similarly, HTTP/2 allows header field values
// that are not valid. While most of the values that can be encoded
// will not alter header field parsing, carriage return (CR, ASCII
// 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII
// 0x0) might be exploited by an attacker if they are translated
// verbatim. Any request or response that contains a character not
// permitted in a header field value MUST be treated as malformed
// (Section 8.1.2.6). Valid characters are defined by the
// field-content ABNF rule in Section 3.2 of [RFC7230]."
//
// This function does not (yet?) properly handle the rejection of
// strings that begin or end with SP or HTAB.
func validHeaderFieldValue(v string) bool {
for i := 0; i < len(v); i++ {
if b := v[i]; b < ' ' && b != '\t' {
return false return false
} }
} }
...@@ -296,3 +340,83 @@ func (e *httpError) Timeout() bool { return e.timeout } ...@@ -296,3 +340,83 @@ func (e *httpError) Timeout() bool { return e.timeout }
func (e *httpError) Temporary() bool { return true } func (e *httpError) Temporary() bool { return true }
var errTimeout error = &httpError{msg: "http2: timeout awaiting response headers", timeout: true} var errTimeout error = &httpError{msg: "http2: timeout awaiting response headers", timeout: true}
var isTokenTable = [127]bool{
'!': true,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'*': true,
'+': true,
'-': true,
'.': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
}
...@@ -501,7 +501,7 @@ func (sc *serverConn) onNewHeaderField(f hpack.HeaderField) { ...@@ -501,7 +501,7 @@ func (sc *serverConn) onNewHeaderField(f hpack.HeaderField) {
sc.vlogf("http2: server decoded %v", f) sc.vlogf("http2: server decoded %v", f)
} }
switch { switch {
case !validHeader(f.Name): case !validHeaderFieldValue(f.Value): // f.Name checked _after_ pseudo check, since ':' is invalid
sc.req.invalidHeader = true sc.req.invalidHeader = true
case strings.HasPrefix(f.Name, ":"): case strings.HasPrefix(f.Name, ":"):
if sc.req.sawRegularHeader { if sc.req.sawRegularHeader {
...@@ -535,6 +535,8 @@ func (sc *serverConn) onNewHeaderField(f hpack.HeaderField) { ...@@ -535,6 +535,8 @@ func (sc *serverConn) onNewHeaderField(f hpack.HeaderField) {
return return
} }
*dst = f.Value *dst = f.Value
case !validHeaderFieldName(f.Name):
sc.req.invalidHeader = true
default: default:
sc.req.sawRegularHeader = true sc.req.sawRegularHeader = true
sc.req.header.Add(sc.canonicalHeader(f.Name), f.Value) sc.req.header.Add(sc.canonicalHeader(f.Name), f.Value)
...@@ -553,10 +555,10 @@ func (st *stream) onNewTrailerField(f hpack.HeaderField) { ...@@ -553,10 +555,10 @@ func (st *stream) onNewTrailerField(f hpack.HeaderField) {
sc.vlogf("http2: server decoded trailer %v", f) sc.vlogf("http2: server decoded trailer %v", f)
} }
switch { switch {
case !validHeader(f.Name): case strings.HasPrefix(f.Name, ":"):
sc.req.invalidHeader = true sc.req.invalidHeader = true
return return
case strings.HasPrefix(f.Name, ":"): case !validHeaderFieldName(f.Name) || !validHeaderFieldValue(f.Value):
sc.req.invalidHeader = true sc.req.invalidHeader = true
return return
default: default:
......
...@@ -844,6 +844,26 @@ func TestServer_Request_Reject_CapitalHeader(t *testing.T) { ...@@ -844,6 +844,26 @@ func TestServer_Request_Reject_CapitalHeader(t *testing.T) {
testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("UPPER", "v") }) testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("UPPER", "v") })
} }
func TestServer_Request_Reject_HeaderFieldNameColon(t *testing.T) {
testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("has:colon", "v") })
}
func TestServer_Request_Reject_HeaderFieldNameNULL(t *testing.T) {
testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("has\x00null", "v") })
}
func TestServer_Request_Reject_HeaderFieldNameEmpty(t *testing.T) {
testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("", "v") })
}
func TestServer_Request_Reject_HeaderFieldValueNewline(t *testing.T) {
testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("foo", "has\nnewline") })
}
func TestServer_Request_Reject_HeaderFieldValueCR(t *testing.T) {
testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("foo", "has\rcarriage") })
}
func TestServer_Request_Reject_Pseudo_Missing_method(t *testing.T) { func TestServer_Request_Reject_Pseudo_Missing_method(t *testing.T) {
testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":method", "") }) testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":method", "") })
} }
......
...@@ -1539,7 +1539,6 @@ func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error) ...@@ -1539,7 +1539,6 @@ func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error)
var ( var (
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit") errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
errInvalidHeaderKey = errors.New("http2: invalid header key")
errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers") errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers")
) )
...@@ -1556,8 +1555,8 @@ func (rl *clientConnReadLoop) checkHeaderField(f hpack.HeaderField) bool { ...@@ -1556,8 +1555,8 @@ func (rl *clientConnReadLoop) checkHeaderField(f hpack.HeaderField) bool {
return false return false
} }
if !validHeader(f.Name) { if !validHeaderFieldValue(f.Value) {
rl.reqMalformed = errInvalidHeaderKey rl.reqMalformed = errInvalidHeaderFieldValue
return false return false
} }
...@@ -1568,6 +1567,10 @@ func (rl *clientConnReadLoop) checkHeaderField(f hpack.HeaderField) bool { ...@@ -1568,6 +1567,10 @@ func (rl *clientConnReadLoop) checkHeaderField(f hpack.HeaderField) bool {
return false return false
} }
} else { } else {
if !validHeaderFieldName(f.Name) {
rl.reqMalformed = errInvalidHeaderFieldName
return false
}
rl.sawRegHeader = true rl.sawRegHeader = true
} }
......
...@@ -1100,11 +1100,21 @@ func TestTransportInvalidTrailer_Capital2(t *testing.T) { ...@@ -1100,11 +1100,21 @@ func TestTransportInvalidTrailer_Capital2(t *testing.T) {
testTransportInvalidTrailer_Capital(t, splitHeader) testTransportInvalidTrailer_Capital(t, splitHeader)
} }
func testTransportInvalidTrailer_Capital(t *testing.T, trailers headerType) { func testTransportInvalidTrailer_Capital(t *testing.T, trailers headerType) {
testInvalidTrailer(t, trailers, errInvalidHeaderKey, func(enc *hpack.Encoder) { testInvalidTrailer(t, trailers, errInvalidHeaderFieldName, func(enc *hpack.Encoder) {
enc.WriteField(hpack.HeaderField{Name: "foo", Value: "bar"}) enc.WriteField(hpack.HeaderField{Name: "foo", Value: "bar"})
enc.WriteField(hpack.HeaderField{Name: "Capital", Value: "bad"}) enc.WriteField(hpack.HeaderField{Name: "Capital", Value: "bad"})
}) })
} }
func TestTransportInvalidTrailer_EmptyFieldName(t *testing.T) {
testInvalidTrailer(t, oneHeader, errInvalidHeaderFieldName, func(enc *hpack.Encoder) {
enc.WriteField(hpack.HeaderField{Name: "", Value: "bad"})
})
}
func TestTransportInvalidTrailer_BinaryFieldValue(t *testing.T) {
testInvalidTrailer(t, oneHeader, errInvalidHeaderFieldValue, func(enc *hpack.Encoder) {
enc.WriteField(hpack.HeaderField{Name: "", Value: "has\nnewline"})
})
}
func testInvalidTrailer(t *testing.T, trailers headerType, wantErr error, writeTrailer func(*hpack.Encoder)) { func testInvalidTrailer(t *testing.T, trailers headerType, wantErr error, writeTrailer func(*hpack.Encoder)) {
ct := newClientTester(t) ct := newClientTester(t)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment