CVE-2024-22189
Description
quic-go is an implementation of the QUIC protocol in Go. Prior to version 0.42.0, an attacker can cause its peer to run out of memory sending a large number of NEW_CONNECTION_ID frames that retire old connection IDs. The receiver is supposed to respond to each retirement frame with a RETIRE_CONNECTION_ID frame. The attacker can prevent the receiver from sending out (the vast majority of) these RETIRE_CONNECTION_ID frames by collapsing the peers congestion window (by selectively acknowledging received packets) and by manipulating the peer's RTT estimate. Version 0.42.0 contains a patch for the issue. No known workarounds are available.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/quic-go/quic-goGo | < 0.42.0 | 0.42.0 |
Patches
14a99b816ae3aclose connection when an abnormally large number of frames are queued (#4369)
3 files changed · +44 −4
connection.go+3 −0 modified@@ -521,6 +521,9 @@ func (s *connection) run() error { runLoop: for { + if s.framer.QueuedTooManyControlFrames() { + s.closeLocal(&qerr.TransportError{ErrorCode: InternalError}) + } // Close immediately if requested select { case closeErr = <-s.closeChan:
framer.go+24 −4 modified@@ -21,9 +21,19 @@ type framer interface { AppendStreamFrames([]ackhandler.StreamFrame, protocol.ByteCount, protocol.Version) ([]ackhandler.StreamFrame, protocol.ByteCount) Handle0RTTRejection() error + + // QueuedTooManyControlFrames says if the control frame queue exceeded its maximum queue length. + // This is a hack. + // It is easier to implement than propagating an error return value in QueueControlFrame. + // The correct solution would be to queue frames with their respective structs. + // See https://github.com/quic-go/quic-go/issues/4271 for the queueing of stream-related control frames. + QueuedTooManyControlFrames() bool } -const maxPathResponses = 256 +const ( + maxPathResponses = 256 + maxControlFrames = 16 << 10 +) type framerI struct { mutex sync.Mutex @@ -33,9 +43,10 @@ type framerI struct { activeStreams map[protocol.StreamID]struct{} streamQueue ringbuffer.RingBuffer[protocol.StreamID] - controlFrameMutex sync.Mutex - controlFrames []wire.Frame - pathResponses []*wire.PathResponseFrame + controlFrameMutex sync.Mutex + controlFrames []wire.Frame + pathResponses []*wire.PathResponseFrame + queuedTooManyControlFrames bool } var _ framer = &framerI{} @@ -73,6 +84,11 @@ func (f *framerI) QueueControlFrame(frame wire.Frame) { f.pathResponses = append(f.pathResponses, pr) return } + // This is a hack. + if len(f.controlFrames) >= maxControlFrames { + f.queuedTooManyControlFrames = true + return + } f.controlFrames = append(f.controlFrames, frame) } @@ -105,6 +121,10 @@ func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol return frames, length } +func (f *framerI) QueuedTooManyControlFrames() bool { + return f.queuedTooManyControlFrames +} + func (f *framerI) AddActiveStream(id protocol.StreamID) { f.mutex.Lock() if _, ok := f.activeStreams[id]; !ok {
framer_test.go+17 −0 modified@@ -109,6 +109,23 @@ var _ = Describe("Framer", func() { Expect(fs).To(HaveLen(2)) Expect(length).To(Equal(ping.Length(version) + ncid.Length(version))) }) + + It("detects when too many frames are queued", func() { + for i := 0; i < maxControlFrames-1; i++ { + framer.QueueControlFrame(&wire.PingFrame{}) + framer.QueueControlFrame(&wire.PingFrame{}) + Expect(framer.QueuedTooManyControlFrames()).To(BeFalse()) + frames, _ := framer.AppendControlFrames([]ackhandler.Frame{}, 1, protocol.Version1) + Expect(frames).To(HaveLen(1)) + Expect(framer.(*framerI).controlFrames).To(HaveLen(i + 1)) + } + framer.QueueControlFrame(&wire.PingFrame{}) + Expect(framer.QueuedTooManyControlFrames()).To(BeFalse()) + Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames)) + framer.QueueControlFrame(&wire.PingFrame{}) + Expect(framer.QueuedTooManyControlFrames()).To(BeTrue()) + Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames)) + }) }) Context("handling PATH_RESPONSE frames", func() {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-c33x-xqrf-c478ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-22189ghsaADVISORY
- github.com/quic-go/quic-go/commit/4a99b816ae3ab03ae5449d15aac45147c85ed47anvdWEB
- github.com/quic-go/quic-go/security/advisories/GHSA-c33x-xqrf-c478nvdWEB
- seemann.io/posts/2024-03-19-exploiting-quics-connection-id-managementnvdWEB
- www.youtube.com/watchnvdWEB
News mentions
0No linked articles in our index yet.