Headline
CVE-2023-46239: fix handling of ACK frames serialized after CRYPTO frames (#4018) · quic-go/quic-go@b6a4725
quic-go is an implementation of the QUIC protocol in Go. Starting in version 0.37.0 and prior to version 0.37.3, by serializing an ACK frame after the CRYTPO that allows a node to complete the handshake, a remote node could trigger a nil pointer dereference (leading to a panic) when the node attempted to drop the Handshake packet number space. An attacker can bring down a quic-go node with very minimal effort. Completing the QUIC handshake only requires sending and receiving a few packets. Version 0.37.3 contains a patch. Versions before 0.37.0 are not affected.
Expand Up @@ -1891,7 +1891,6 @@ var _ = Describe("Connection", func() {
It("cancels the HandshakeComplete context when the handshake completes", func() { packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes() finishHandshake := make(chan struct{}) sph := mockackhandler.NewMockSentPacketHandler(mockCtrl) conn.sentPacketHandler = sph tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake) Expand All @@ -1901,53 +1900,26 @@ var _ = Describe("Connection", func() { sph.EXPECT().DropPackets(protocol.EncryptionHandshake) sph.EXPECT().SetHandshakeConfirmed() connRunner.EXPECT().Retire(clientDestConnID) go func() { defer GinkgoRecover() <-finishHandshake cryptoSetup.EXPECT().StartHandshake() cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete}) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}) cryptoSetup.EXPECT().SetHandshakeConfirmed() cryptoSetup.EXPECT().GetSessionTicket() conn.run() }() cryptoSetup.EXPECT().SetHandshakeConfirmed() cryptoSetup.EXPECT().GetSessionTicket() handshakeCtx := conn.HandshakeComplete() Consistently(handshakeCtx).ShouldNot(BeClosed()) close(finishHandshake) Expect(conn.handleHandshakeComplete()).To(Succeed()) Eventually(handshakeCtx).Should(BeClosed()) // make sure the go routine returns streamManager.EXPECT().CloseWithError(gomock.Any()) expectReplaceWithClosed() packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil) cryptoSetup.EXPECT().Close() mconn.EXPECT().Write(gomock.Any(), gomock.Any()) tracer.EXPECT().ClosedConnection(gomock.Any()) tracer.EXPECT().Close() conn.shutdown() Eventually(conn.Context().Done()).Should(BeClosed()) })
It("sends a session ticket when the handshake completes", func() { const size = protocol.MaxPostHandshakeCryptoFrameSize * 3 / 2 packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes() finishHandshake := make(chan struct{}) connRunner.EXPECT().Retire(clientDestConnID) conn.sentPacketHandler.DropPackets(protocol.EncryptionInitial) tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake) go func() { defer GinkgoRecover() <-finishHandshake cryptoSetup.EXPECT().StartHandshake() cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete}) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}) cryptoSetup.EXPECT().SetHandshakeConfirmed() cryptoSetup.EXPECT().GetSessionTicket().Return(make([]byte, size), nil) conn.run() }() cryptoSetup.EXPECT().SetHandshakeConfirmed() cryptoSetup.EXPECT().GetSessionTicket().Return(make([]byte, size), nil)
handshakeCtx := conn.HandshakeComplete() Consistently(handshakeCtx).ShouldNot(BeClosed()) close(finishHandshake) Expect(conn.handleHandshakeComplete()).To(Succeed()) var frames []ackhandler.Frame Eventually(func() []ackhandler.Frame { frames, _ = conn.framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1) Expand All @@ -1963,16 +1935,6 @@ var _ = Describe("Connection", func() { } } Expect(size).To(BeEquivalentTo(s)) // make sure the go routine returns streamManager.EXPECT().CloseWithError(gomock.Any()) expectReplaceWithClosed() packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil) cryptoSetup.EXPECT().Close() mconn.EXPECT().Write(gomock.Any(), gomock.Any()) tracer.EXPECT().ClosedConnection(gomock.Any()) tracer.EXPECT().Close() conn.shutdown() Eventually(conn.Context().Done()).Should(BeClosed()) })
It("doesn’t cancel the HandshakeComplete context when the handshake fails", func() { Expand Down Expand Up @@ -2027,6 +1989,7 @@ var _ = Describe("Connection", func() { cryptoSetup.EXPECT().SetHandshakeConfirmed() cryptoSetup.EXPECT().GetSessionTicket() mconn.EXPECT().Write(gomock.Any(), gomock.Any()) Expect(conn.handleHandshakeComplete()).To(Succeed()) conn.run() }() Eventually(done).Should(BeClosed()) Expand Down Expand Up @@ -2350,6 +2313,7 @@ var _ = Describe("Connection", func() { cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}) cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1) cryptoSetup.EXPECT().SetHandshakeConfirmed().MaxTimes(1) Expect(conn.handleHandshakeComplete()).To(Succeed()) err := conn.run() nerr, ok := err.(net.Error) Expect(ok).To(BeTrue()) Expand Down Expand Up @@ -2867,7 +2831,10 @@ var _ = Describe("Client Connection", func() { TransportParameters: params, }) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete}).MaxTimes(1) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}).MaxTimes(1) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}).MaxTimes(1).Do(func() { defer GinkgoRecover() Expect(conn.handleHandshakeComplete()).To(Succeed()) }) errChan <- conn.run() close(errChan) }() Expand Down
Related news
quic-go is an implementation of the [QUIC](https://datatracker.ietf.org/doc/html/rfc9000) transport protocol in Go. By serializing an ACK frame after the CRYTPO that allows a node to complete the handshake, a remote node could trigger a nil pointer dereference (leading to a panic) when the node attempted to drop the Handshake packet number space. **Impact** An attacker can bring down a quic-go node with very minimal effort. Completing the QUIC handshake only requires sending and receiving a few packets. **Patches** [v0.37.3](https://github.com/quic-go/quic-go/releases/tag/v0.37.3) contains a patch. Versions before v0.37.0 are not affected.