From 3311514d67ae6ac1ca02db8da6877613c9ca5353 Mon Sep 17 00:00:00 2001 From: pittgi <68589027+pittgi@users.noreply.github.com> Date: Sun, 16 Mar 2025 03:52:25 +0100 Subject: [PATCH] http3: reject duplicate pseudo headers (#4993) --- http3/headers.go | 12 +++++++++++- http3/headers_test.go | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/http3/headers.go b/http3/headers.go index 05d13ff3c..27af95003 100644 --- a/http3/headers.go +++ b/http3/headers.go @@ -55,24 +55,34 @@ func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) { // all pseudo headers must appear before regular header fields, see section 4.3 of RFC 9114 return header{}, fmt.Errorf("received pseudo header %s after a regular header field", h.Name) } - var isResponsePseudoHeader bool // pseudo headers are either valid for requests or for responses + var isResponsePseudoHeader bool // pseudo headers are either valid for requests or for responses + var isDuplicatePseudoHeader bool // pseudo headers are allowed to appear exactly once switch h.Name { case ":path": + isDuplicatePseudoHeader = hdr.Path != "" hdr.Path = h.Value case ":method": + isDuplicatePseudoHeader = hdr.Method != "" hdr.Method = h.Value case ":authority": + isDuplicatePseudoHeader = hdr.Authority != "" hdr.Authority = h.Value case ":protocol": + isDuplicatePseudoHeader = hdr.Protocol != "" hdr.Protocol = h.Value case ":scheme": + isDuplicatePseudoHeader = hdr.Scheme != "" hdr.Scheme = h.Value case ":status": + isDuplicatePseudoHeader = hdr.Status != "" hdr.Status = h.Value isResponsePseudoHeader = true default: return header{}, fmt.Errorf("unknown pseudo header: %s", h.Name) } + if isDuplicatePseudoHeader { + return header{}, fmt.Errorf("duplicate pseudo header: %s", h.Name) + } if isRequest && isResponsePseudoHeader { return header{}, fmt.Errorf("invalid request pseudo header: %s", h.Name) } diff --git a/http3/headers_test.go b/http3/headers_test.go index a1cedc82f..3f23cc3b9 100644 --- a/http3/headers_test.go +++ b/http3/headers_test.go @@ -235,6 +235,18 @@ var _ = Describe("Request", func() { Expect(err).To(MatchError(":protocol must be empty")) }) + It("errors with duplicate pseudo header in request", func() { + headers := []qpack.HeaderField{ + {Name: ":path", Value: "/foo"}, + {Name: ":authority", Value: "quic.clemente.io"}, + {Name: ":method", Value: "GET"}, + {Name: ":scheme", Value: "https"}, + {Name: ":method", Value: "POST"}, + } + _, err := requestFromHeaders(headers) + Expect(err).To(MatchError("duplicate pseudo header: :method")) + }) + Context("regular HTTP CONNECT", func() { It("handles CONNECT method", func() { headers := []qpack.HeaderField{ @@ -432,4 +444,13 @@ var _ = Describe("Response", func() { _, err := parseTrailers(headers) Expect(err).To(MatchError("http3: received pseudo header in trailer: :status")) }) + + It("errors with duplicate status header in response", func() { + headers := []qpack.HeaderField{ + {Name: ":status", Value: "200"}, + {Name: ":status", Value: "400"}, + } + err := updateResponseFromHeaders(&http.Response{}, headers) + Expect(err).To(MatchError("duplicate pseudo header: :status")) + }) })