quic-go HTTP/3 QPACK Header Expansion DoS
Description
quic-go is an implementation of the QUIC protocol in Go. Versions 0.56.0 and below are vulnerable to excessive memory allocation through quic-go's HTTP/3 client and server implementations by sending a QPACK-encoded HEADERS frame that decodes into a large header field section (many unique header names and/or large values). The implementation builds an http.Header (used on the http.Request and http.Response, respectively), while only enforcing limits on the size of the (QPACK-compressed) HEADERS frame, but not on the decoded header, leading to memory exhaustion. This issue is fixed in version 0.57.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A memory exhaustion vulnerability in quic-go before 0.57.0 allows remote attackers to cause denial of service via QPACK-encoded HTTP/3 HEADERS frames that expand to large decoded headers.
Root
Cause
The vulnerability resides in quic-go's handling of QPACK-encoded HTTP/3 HEADERS frames [1][4]. The implementation limits the size of the compressed HEADERS frame (via MaxHeaderBytes), but does not enforce a limit on the size after QPACK decompression [2][4]. A crafted QPACK static table entry can expand to roughly 50 times its compressed size, leading to excessive memory allocation when building the http.Header map for http.Request or http.Response [4].
Attack
Vector
An unauthenticated remote attacker can send a malicious HTTP/3 HEADERS frame to a vulnerable server or client [2][4]. No special network position is required beyond being able to establish a QUIC connection and send HTTP/3 frames. The attack works symmetrically: both server and client construct the same http.Header structure, making both sides equally exposed [4]. The compressed frame may pass the enforced size limits, while the decoded version exhausts memory.
Impact
Successful exploitation causes excessive memory allocation, leading to memory exhaustion on the target host [2][4]. This results in a denial-of-service (DoS) condition, potentially crashing the application or making it unresponsive [2][4]. The vulnerability affects all versions of quic-go up to and including 0.56.0 [2].
Mitigation
The fix is implemented in quic-go version 0.57.0 [2]. The patch introduces enforcement of the decoded field section size limits per RFC 9114, sending SETTINGS_MAX_FIELD_SECTION_SIZE and performing incremental QPACK decoding to abort early when the header size exceeds the limit [1][4]. Users should upgrade to version 0.57.0 or later. No workaround is documented for older versions.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/quic-go/quic-goGo | < 0.57.0 | 0.57.0 |
Affected products
2- quic-go/quic-gov5Range: < 0.57.0
Patches
15b2d2129f831http3: limit size of decompressed headers (#5452)
7 files changed · +198 −37
http3/conn.go+1 −1 modified@@ -196,7 +196,7 @@ func (c *Conn) openRequestStream( func (c *Conn) decodeTrailers(r io.Reader, streamID quic.StreamID, hf *headersFrame, maxHeaderBytes int) (http.Header, error) { if hf.Length > uint64(maxHeaderBytes) { maybeQlogInvalidHeadersFrame(c.qlogger, streamID, hf.Length) - return nil, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", hf.Length, maxHeaderBytes) + return nil, fmt.Errorf("http3: HEADERS frame too large: %d bytes (max: %d)", hf.Length, maxHeaderBytes) } b := make([]byte, hf.Length)
http3/headers.go+14 −5 modified@@ -20,6 +20,8 @@ type qpackError struct{ err error } func (e *qpackError) Error() string { return fmt.Sprintf("qpack: %v", e.err) } func (e *qpackError) Unwrap() error { return e.err } +var errHeaderTooLarge = errors.New("http3: headers too large") + type header struct { // Pseudo header fields defined in RFC 9114 Path string @@ -44,7 +46,7 @@ var invalidHeaderFields = [...]string{ "upgrade", } -func parseHeaders(decodeFn qpack.DecodeFunc, isRequest bool, headerFields *[]qpack.HeaderField) (header, error) { +func parseHeaders(decodeFn qpack.DecodeFunc, isRequest bool, sizeLimit int, headerFields *[]qpack.HeaderField) (header, error) { hdr := header{Headers: make(http.Header)} var readFirstRegularHeader, readContentLength bool var contentLengthStr string @@ -59,6 +61,13 @@ func parseHeaders(decodeFn qpack.DecodeFunc, isRequest bool, headerFields *[]qpa if headerFields != nil { *headerFields = append(*headerFields, h) } + // RFC 9114, section 4.2.2: + // The size of a field list is calculated based on the uncompressed size of fields, + // including the length of the name and value in bytes plus an overhead of 32 bytes for each field. + sizeLimit -= len(h.Name) + len(h.Value) + 32 + if sizeLimit < 0 { + return header{}, errHeaderTooLarge + } // field names need to be lowercase, see section 4.2 of RFC 9114 if strings.ToLower(h.Name) != h.Name { return header{}, fmt.Errorf("header field is not lower-case: %s", h.Name) @@ -167,8 +176,8 @@ func parseTrailers(decodeFn qpack.DecodeFunc, headerFields *[]qpack.HeaderField) return h, nil } -func requestFromHeaders(decodeFn qpack.DecodeFunc, headerFields *[]qpack.HeaderField) (*http.Request, error) { - hdr, err := parseHeaders(decodeFn, true, headerFields) +func requestFromHeaders(decodeFn qpack.DecodeFunc, sizeLimit int, headerFields *[]qpack.HeaderField) (*http.Request, error) { + hdr, err := parseHeaders(decodeFn, true, sizeLimit, headerFields) if err != nil { return nil, err } @@ -241,8 +250,8 @@ func requestFromHeaders(decodeFn qpack.DecodeFunc, headerFields *[]qpack.HeaderF // using the decoded qpack header filed. // It is only called for the HTTP header (and not the HTTP trailer). // It takes an http.Response as an argument to allow the caller to set the trailer later on. -func updateResponseFromHeaders(rsp *http.Response, decodeFn qpack.DecodeFunc, headerFields *[]qpack.HeaderField) error { - hdr, err := parseHeaders(decodeFn, false, headerFields) +func updateResponseFromHeaders(rsp *http.Response, decodeFn qpack.DecodeFunc, sizeLimit int, headerFields *[]qpack.HeaderField) error { + hdr, err := parseHeaders(decodeFn, false, sizeLimit, headerFields) if err != nil { return err }
http3/headers_test.go+21 −20 modified@@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "math" "net/http" "testing" @@ -41,7 +42,7 @@ func testRequestHeaderParsing(t *testing.T, path string) { {Name: ":method", Value: http.MethodGet}, {Name: "content-length", Value: "42"}, } - req, err := requestFromHeaders(decodeFromSlice(headers), nil) + req, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.NoError(t, err) require.Equal(t, http.MethodGet, req.Method) require.Equal(t, path, req.URL.Path) @@ -64,7 +65,7 @@ func TestRequestHeadersContentLength(t *testing.T) { {Name: ":authority", Value: "quic-go.net"}, {Name: ":method", Value: http.MethodGet}, } - req, err := requestFromHeaders(decodeFromSlice(headers), nil) + req, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.NoError(t, err) require.Equal(t, int64(-1), req.ContentLength) }) @@ -77,7 +78,7 @@ func TestRequestHeadersContentLength(t *testing.T) { {Name: "content-length", Value: "42"}, {Name: "content-length", Value: "42"}, } - req, err := requestFromHeaders(decodeFromSlice(headers), nil) + req, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.NoError(t, err) require.Equal(t, "42", req.Header.Get("Content-Length")) }) @@ -107,7 +108,7 @@ func TestRequestHeadersContentLengthValidation(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - _, err := requestFromHeaders(decodeFromSlice(tc.headers), nil) + _, err := requestFromHeaders(decodeFromSlice(tc.headers), math.MaxInt, nil) if tc.errContains != "" { require.ErrorContains(t, err, tc.errContains) } @@ -228,7 +229,7 @@ func TestRequestHeadersValidation(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - _, err := requestFromHeaders(decodeFromSlice(tc.headers), nil) + _, err := requestFromHeaders(decodeFromSlice(tc.headers), math.MaxInt, nil) require.EqualError(t, err, tc.err) require.NotErrorAs(t, err, new(*qpackError)) }) @@ -243,7 +244,7 @@ func TestCookieHeader(t *testing.T) { {Name: "cookie", Value: "cookie1=foobar1"}, {Name: "cookie", Value: "cookie2=foobar2"}, } - req, err := requestFromHeaders(decodeFromSlice(headers), nil) + req, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.NoError(t, err) require.Equal(t, http.Header{ "Cookie": []string{"cookie1=foobar1; cookie2=foobar2"}, @@ -259,7 +260,7 @@ func TestHeadersConcatenation(t *testing.T) { {Name: "duplicate-header", Value: "1"}, {Name: "duplicate-header", Value: "2"}, } - req, err := requestFromHeaders(decodeFromSlice(headers), nil) + req, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.NoError(t, err) require.Equal(t, http.Header{ "Cache-Control": []string{"max-age=0"}, @@ -272,7 +273,7 @@ func TestRequestHeadersConnect(t *testing.T) { {Name: ":authority", Value: "quic-go.net"}, {Name: ":method", Value: http.MethodConnect}, } - req, err := requestFromHeaders(decodeFromSlice(headers), nil) + req, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.NoError(t, err) require.Equal(t, http.MethodConnect, req.Method) require.Equal(t, "HTTP/3.0", req.Proto) @@ -302,7 +303,7 @@ func TestRequestHeadersConnectValidation(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - _, err := requestFromHeaders(decodeFromSlice(tc.headers), nil) + _, err := requestFromHeaders(decodeFromSlice(tc.headers), math.MaxInt, nil) require.EqualError(t, err, tc.err) }) } @@ -316,7 +317,7 @@ func TestRequestHeadersExtendedConnect(t *testing.T) { {Name: ":authority", Value: "quic-go.net"}, {Name: ":path", Value: "/foo?val=1337"}, } - req, err := requestFromHeaders(decodeFromSlice(headers), nil) + req, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.NoError(t, err) require.Equal(t, http.MethodConnect, req.Method) require.Equal(t, "webtransport", req.Proto) @@ -331,7 +332,7 @@ func TestRequestHeadersExtendedConnectRequestValidation(t *testing.T) { {Name: ":authority", Value: "quic.clemente.io"}, {Name: ":path", Value: "/foo"}, } - _, err := requestFromHeaders(decodeFromSlice(headers), nil) + _, err := requestFromHeaders(decodeFromSlice(headers), math.MaxInt, nil) require.EqualError(t, err, "extended CONNECT: :scheme, :path and :authority must not be empty") } @@ -341,7 +342,7 @@ func TestResponseHeaderParsing(t *testing.T) { {Name: "content-length", Value: "42"}, } rsp := &http.Response{} - require.NoError(t, updateResponseFromHeaders(rsp, decodeFromSlice(headers), nil)) + require.NoError(t, updateResponseFromHeaders(rsp, decodeFromSlice(headers), math.MaxInt, nil)) require.Equal(t, "HTTP/3.0", rsp.Proto) require.Equal(t, 3, rsp.ProtoMajor) require.Zero(t, rsp.ProtoMinor) @@ -391,7 +392,7 @@ func TestResponseHeaderParsingValidation(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - err := updateResponseFromHeaders(&http.Response{}, decodeFromSlice(tc.headers), nil) + err := updateResponseFromHeaders(&http.Response{}, decodeFromSlice(tc.headers), math.MaxInt, nil) if tc.errContains != "" { require.ErrorContains(t, err, tc.errContains) } @@ -416,7 +417,7 @@ func TestResponseHeaderParsingValidation(t *testing.T) { {Name: ":status", Value: "404"}, {Name: tc.invalidField, Value: "some-value"}, } - err := updateResponseFromHeaders(&http.Response{}, decodeFromSlice(headers), nil) + err := updateResponseFromHeaders(&http.Response{}, decodeFromSlice(headers), math.MaxInt, nil) require.EqualError(t, err, fmt.Sprintf("invalid header field name: %q", tc.invalidField)) }) } @@ -429,7 +430,7 @@ func TestResponseTrailerFields(t *testing.T) { {Name: "trailer", Value: "TRAILER3"}, } var rsp http.Response - require.NoError(t, updateResponseFromHeaders(&rsp, decodeFromSlice(headers), nil)) + require.NoError(t, updateResponseFromHeaders(&rsp, decodeFromSlice(headers), math.MaxInt, nil)) require.Equal(t, 0, len(rsp.Header)) require.Equal(t, http.Header(map[string][]string{ "Trailer1": nil, @@ -443,13 +444,13 @@ func TestResponseTrailerParsingTE(t *testing.T) { {Name: ":status", Value: "404"}, {Name: "te", Value: "trailers"}, } - require.NoError(t, updateResponseFromHeaders(&http.Response{}, decodeFromSlice(headers), nil)) + require.NoError(t, updateResponseFromHeaders(&http.Response{}, decodeFromSlice(headers), math.MaxInt, nil)) headers = []qpack.HeaderField{ {Name: ":status", Value: "404"}, {Name: "te", Value: "not-trailers"}, } require.EqualError(t, - updateResponseFromHeaders(&http.Response{}, decodeFromSlice(headers), nil), + updateResponseFromHeaders(&http.Response{}, decodeFromSlice(headers), math.MaxInt, nil), `invalid TE header field value: "not-trailers"`) } @@ -478,14 +479,14 @@ func TestQpackError(t *testing.T) { t.Run("header parsing", func(t *testing.T) { dec := qpack.NewDecoder() decodeFn := dec.Decode(buf.Bytes()[:len(buf.Bytes())/2]) - _, err := requestFromHeaders(decodeFn, nil) + _, err := requestFromHeaders(decodeFn, math.MaxInt, nil) require.ErrorAs(t, err, new(*qpackError)) }) t.Run("trailer parsing", func(t *testing.T) { dec := qpack.NewDecoder() decodeFn := dec.Decode(buf.Bytes()[:len(buf.Bytes())/2]) - _, err := parseTrailers(decodeFn, nil) + err := updateResponseFromHeaders(&http.Response{}, decodeFn, math.MaxInt, nil) require.ErrorAs(t, err, new(*qpackError)) }) } @@ -517,7 +518,7 @@ func BenchmarkRequestFromHeaders(b *testing.B) { dec := qpack.NewDecoder() for b.Loop() { decodeFn := dec.Decode(buf.Bytes()) - if _, err := requestFromHeaders(decodeFn, nil); err != nil { + if _, err := requestFromHeaders(decodeFn, math.MaxInt, nil); err != nil { b.Fatalf("failed to parse request: %v", err) } }
http3/server.go+25 −7 modified@@ -572,16 +572,16 @@ func (s *Server) handleConn(conn *quic.Conn) error { return handleErr } -func (s *Server) maxHeaderBytes() uint64 { +func (s *Server) maxHeaderBytes() int { if s.MaxHeaderBytes <= 0 { return http.DefaultMaxHeaderBytes } - return uint64(s.MaxHeaderBytes) + return s.MaxHeaderBytes } func (s *Server) handleRequest( conn *Conn, - str datagramStream, + str *stateTrackingStream, decoder *qpack.Decoder, qlogger qlogwriter.Recorder, ) { @@ -610,10 +610,12 @@ func (s *Server) handleRequest( conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "expected first frame to be a HEADERS frame") return } - if hf.Length > s.maxHeaderBytes() { + if hf.Length > uint64(s.maxHeaderBytes()) { maybeQlogInvalidHeadersFrame(qlogger, str.StreamID(), hf.Length) - str.CancelRead(quic.StreamErrorCode(ErrCodeFrameError)) - str.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError)) + // stop the client from sending more data + str.CancelRead(quic.StreamErrorCode(ErrCodeExcessiveLoad)) + // send a 431 Response (Request Header Fields Too Large) + s.rejectWithHeaderFieldsTooLarge(str, conn, qlogger) return } headerBlock := make([]byte, hf.Length) @@ -628,11 +630,19 @@ func (s *Server) handleRequest( if qlogger != nil { hfs = make([]qpack.HeaderField, 0, 16) } - req, err := requestFromHeaders(decodeFn, &hfs) + req, err := requestFromHeaders(decodeFn, s.maxHeaderBytes(), &hfs) if qlogger != nil { qlogParsedHeadersFrame(qlogger, str.StreamID(), hf, hfs) } if err != nil { + if errors.Is(err, errHeaderTooLarge) { + // stop the client from sending more data + str.CancelRead(quic.StreamErrorCode(ErrCodeExcessiveLoad)) + // send a 431 Response (Request Header Fields Too Large) + s.rejectWithHeaderFieldsTooLarge(str, conn, qlogger) + return + } + errCode := ErrCodeMessageError var qpackErr *qpackError if errors.As(err, &qpackErr) { @@ -719,6 +729,14 @@ func (s *Server) handleRequest( str.Close() } +func (s *Server) rejectWithHeaderFieldsTooLarge(str *stateTrackingStream, conn *Conn, qlogger qlogwriter.Recorder) { + hstr := newStream(str, conn, nil, nil, qlogger) + defer hstr.Close() + r := newResponseWriter(hstr, conn, false, s.Logger) + r.WriteHeader(http.StatusRequestHeaderFieldsTooLarge) + r.Flush() +} + // Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients. // Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established. // It is the caller's responsibility to close any connection passed to ServeQUICConn.
http3/server_test.go+3 −2 modified@@ -415,8 +415,9 @@ func testServerRequestHeaderTooLarge(t *testing.T, req *http.Request, maxHeaderB go s.ServeQUICConn(serverConn) - expectStreamReadReset(t, str, quic.StreamErrorCode(ErrCodeFrameError)) - expectStreamWriteReset(t, str, quic.StreamErrorCode(ErrCodeFrameError)) + hfs := decodeHeader(t, str) + require.Equal(t, []string{"431"}, hfs[":status"]) + expectStreamWriteReset(t, str, quic.StreamErrorCode(ErrCodeExcessiveLoad)) require.False(t, called) }
http3/stream.go+1 −1 modified@@ -348,7 +348,7 @@ func (s *RequestStream) ReadResponse() (*http.Response, error) { hfs = make([]qpack.HeaderField, 0, 16) } res := s.response - err = updateResponseFromHeaders(res, decodeFn, &hfs) + err = updateResponseFromHeaders(res, decodeFn, s.maxHeaderBytes, &hfs) if s.str.qlogger != nil { qlogParsedHeadersFrame(s.str.qlogger, s.str.StreamID(), hf, hfs) }
integrationtests/self/http_test.go+133 −1 modified@@ -25,7 +25,9 @@ import ( "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" + "github.com/quic-go/quic-go/http3/qlog" quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy" + "github.com/quic-go/quic-go/testutils/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -79,12 +81,15 @@ func startHTTPServer(t *testing.T, mux *http.ServeMux, opts ...func(*http3.Serve return conn.LocalAddr().(*net.UDPAddr).Port } -func newHTTP3Client(t *testing.T) *http.Client { +func newHTTP3Client(t *testing.T, opts ...func(*http3.Transport)) *http.Client { tr := &http3.Transport{ TLSClientConfig: getTLSClientConfigWithoutServerName(), QUICConfig: getQuicConfig(&quic.Config{MaxIdleTimeout: 10 * time.Second}), DisableCompression: true, } + for _, opt := range opts { + opt(tr) + } addDialCallback(t, tr) t.Cleanup(func() { tr.Close() }) return &http.Client{Transport: tr} @@ -243,6 +248,133 @@ func TestHTTPHeaders(t *testing.T) { require.Equal(t, echoHdr, resp.Header.Get("echo")) } +func TestHTTPHeaderSizeLimitServer(t *testing.T) { + t.Run("large HEADERS frame", func(t *testing.T) { + const limit = 1024 + hdr := make(http.Header) + for range 20 { + hdr.Add(randomString(50), randomString(50)) + } + headersFrameSize := testHTTPHeaderSizeLimitServer(t, hdr, limit) + require.Greater(t, headersFrameSize, limit) + }) + + t.Run("large decompressed HEADERS frame", func(t *testing.T) { + const limit = 1024 + hdr := make(http.Header) + for range 200 { + // This is a QPACK static table entry, so it will be compressed. + hdr.Add("content-type", "text/plain;charset=utf-8") + } + headersFrameSize := testHTTPHeaderSizeLimitServer(t, hdr, limit) + require.Less(t, headersFrameSize, limit) + }) +} + +func testHTTPHeaderSizeLimitServer(t *testing.T, hdr http.Header, limit int) (headersFrameSize int) { + mux := http.NewServeMux() + var handlerCalled bool + mux.HandleFunc("/headers", func(w http.ResponseWriter, r *http.Request) { + handlerCalled = true + }) + port := startHTTPServer(t, mux, func(s *http3.Server) { s.MaxHeaderBytes = limit }) + + var eventRecorder events.Recorder + cl := newHTTP3Client(t, func(tr *http3.Transport) { + tr.QUICConfig = getQuicConfig(&quic.Config{ + MaxIdleTimeout: 10 * time.Second, + Tracer: newTracer(&eventRecorder), + }) + }) + + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/headers", port), nil) + require.NoError(t, err) + req.Header = hdr + + resp, err := cl.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusRequestHeaderFieldsTooLarge, resp.StatusCode) + require.False(t, handlerCalled) + + for _, ev := range eventRecorder.Events(qlog.FrameCreated{}) { + fc := ev.(qlog.FrameCreated) + if _, ok := fc.Frame.Frame.(qlog.HeadersFrame); ok { + headersFrameSize = fc.Raw.Length + break + } + } + return headersFrameSize +} + +func TestHTTPHeaderSizeLimitClient(t *testing.T) { + t.Run("large HEADERS frame", func(t *testing.T) { + const limit = 1024 + hdr := make(http.Header) + for range 20 { + hdr.Add(randomString(50), randomString(50)) + } + headersFrameSize, requestErr := testHTTPHeaderSizeLimitClient(t, hdr, limit) + require.ErrorContains(t, requestErr, "http3: HEADERS frame too large") + require.Greater(t, headersFrameSize, limit) + }) + + t.Run("large decompressed HEADERS frame", func(t *testing.T) { + const limit = 1024 + hdr := make(http.Header) + for range 200 { + // This is a QPACK static table entry, so it will be compressed. + hdr.Add("content-type", "text/plain;charset=utf-8") + } + headersFrameSize, requestErr := testHTTPHeaderSizeLimitClient(t, hdr, limit) + require.ErrorContains(t, requestErr, "http3: headers too large") + require.Less(t, headersFrameSize, limit) + }) +} + +func testHTTPHeaderSizeLimitClient(t *testing.T, hdr http.Header, limit int) (headersFrameSize int, requestErr error) { + mux := http.NewServeMux() + var handlerCalled atomic.Bool + mux.HandleFunc("/headers", func(w http.ResponseWriter, r *http.Request) { + handlerCalled.Store(true) + for k, v := range hdr { + for _, val := range v { + w.Header().Add(k, val) + } + } + }) + port := startHTTPServer(t, mux) + + var eventRecorder events.Recorder + cl := newHTTP3Client(t, + func(tr *http3.Transport) { + tr.MaxResponseHeaderBytes = limit + tr.QUICConfig = getQuicConfig(&quic.Config{ + MaxIdleTimeout: 10 * time.Second, + Tracer: newTracer(&eventRecorder), + }) + }, + ) + + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/headers", port), nil) + require.NoError(t, err) + + _, requestErr = cl.Do(req) + require.Error(t, requestErr) + require.True(t, handlerCalled.Load()) + + var found bool + for _, ev := range eventRecorder.Events(qlog.FrameParsed{}) { + fp := ev.(qlog.FrameParsed) + if _, ok := fp.Frame.Frame.(qlog.HeadersFrame); ok { + headersFrameSize = fp.Raw.PayloadLength + found = true + break + } + } + require.True(t, found) + return headersFrameSize, requestErr +} + func TestHTTPTrailers(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/trailers", func(w http.ResponseWriter, r *http.Request) {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-g754-hx8w-x2g6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-64702ghsaADVISORY
- github.com/quic-go/quic-go/commit/5b2d2129f8315da41e01eff0a847ab38a34e83a8ghsax_refsource_MISCWEB
- github.com/quic-go/quic-go/security/advisories/GHSA-g754-hx8w-x2g6ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.