CVE-2024-22189 (Go, published 2024-04-04T15:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/quic-go/quic-go
Files changed:
connection.go
framer.go
framer_test.go
Commit message: close connection when an abnormally large number of frames are queued (#4369)

Under normal circumstances, we should be able to send out control frames
right away, so we don't expect any queue to build up. To defend against
resource exhaustion attacks, we limit the control frame queue to 16384
elements.

Vulnerable code:
const maxPathResponses = 256
	controlFrameMutex sync.Mutex
	controlFrames     []wire.Frame
	pathResponses     []*wire.PathResponseFrame

Fixed code:
		if s.framer.QueuedTooManyControlFrames() {
			s.closeLocal(&qerr.TransportError{ErrorCode: InternalError})
		}

	// 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
	maxControlFrames = 16 << 10
)
	controlFrameMutex          sync.Mutex
	controlFrames              []wire.Frame
	pathResponses              []*wire.PathResponseFrame
	queuedTooManyControlFrames bool
	// This is a hack.
	if len(f.controlFrames) >= maxControlFrames {
		f.queuedTooManyControlFrames = true
		return
	}
func (f *framerI) QueuedTooManyControlFrames() bool {
	return f.queuedTooManyControlFrames
}


		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))
		})