From 920bfb46af9db2ab23a94e54701b7a310ecc4928 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 23 Aug 2024 17:42:18 +0700 Subject: [PATCH] http3: reject pseudo header fields in trailers (#4639) * http3: reject pseudo header fields in trailers As defined in section 4.3 of RFC 9114. * http3: improve allocs for trailer map * http3: rename connection.parseTrailer to decodeTrailers --- http3/conn.go | 10 +++------- http3/headers.go | 11 +++++++++++ http3/headers_test.go | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/http3/conn.go b/http3/conn.go index 5c12b704e..0f372b0dd 100644 --- a/http3/conn.go +++ b/http3/conn.go @@ -116,7 +116,7 @@ func (c *connection) openRequestStream( qstr := newStateTrackingStream(str, c, datagrams) rsp := &http.Response{} hstr := newStream(qstr, c, datagrams, func(r io.Reader, l uint64) error { - hdr, err := c.parseTrailer(r, l, maxHeaderBytes) + hdr, err := c.decodeTrailers(r, l, maxHeaderBytes) if err != nil { return err } @@ -126,7 +126,7 @@ func (c *connection) openRequestStream( return newRequestStream(hstr, requestWriter, reqDone, c.decoder, disableCompression, maxHeaderBytes, rsp), nil } -func (c *connection) parseTrailer(r io.Reader, l, maxHeaderBytes uint64) (http.Header, error) { +func (c *connection) decodeTrailers(r io.Reader, l, maxHeaderBytes uint64) (http.Header, error) { if l > maxHeaderBytes { return nil, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", l, maxHeaderBytes) } @@ -139,11 +139,7 @@ func (c *connection) parseTrailer(r io.Reader, l, maxHeaderBytes uint64) (http.H if err != nil { return nil, err } - h := http.Header{} - for _, field := range fields { - h.Add(field.Name, field.Value) - } - return h, nil + return parseTrailers(fields) } func (c *connection) acceptStream(ctx context.Context) (quic.Stream, *datagrammer, error) { diff --git a/http3/headers.go b/http3/headers.go index fd50d04a1..c51f48d00 100644 --- a/http3/headers.go +++ b/http3/headers.go @@ -101,6 +101,17 @@ func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) { return hdr, nil } +func parseTrailers(headers []qpack.HeaderField) (http.Header, error) { + h := make(http.Header, len(headers)) + for _, field := range headers { + if field.IsPseudo() { + return nil, fmt.Errorf("http3: received pseudo header in trailer: %s", field.Name) + } + h.Add(field.Name, field.Value) + } + return h, nil +} + func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error) { hdr, err := parseHeaders(headerFields, true) if err != nil { diff --git a/http3/headers_test.go b/http3/headers_test.go index b1b52f5a0..65fa38370 100644 --- a/http3/headers_test.go +++ b/http3/headers_test.go @@ -358,4 +358,21 @@ var _ = Describe("Response", func() { err := updateResponseFromHeaders(&http.Response{}, headers) Expect(err).To(MatchError("invalid response pseudo header: :method")) }) + + It("parses trailers", func() { + headers := []qpack.HeaderField{ + {Name: "content-length", Value: "42"}, + } + hdr, err := parseTrailers(headers) + Expect(err).ToNot(HaveOccurred()) + Expect(hdr.Get("Content-Length")).To(Equal("42")) + }) + + It("parses trailers", func() { + headers := []qpack.HeaderField{ + {Name: ":status", Value: "200"}, + } + _, err := parseTrailers(headers) + Expect(err).To(MatchError("http3: received pseudo header in trailer: :status")) + }) })