146 lines
5.0 KiB
Diff
146 lines
5.0 KiB
Diff
From ab95d15e0df73dd533aa48e67ddb984d1ab4b60f Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Dan=20=C4=8Cerm=C3=A1k?= <dcermak@suse.com>
|
|
Date: Mon, 17 Mar 2025 10:37:21 +0100
|
|
Subject: [PATCH 1/2] CVE-2025-22869: vendor/ssh: limit the size of the
|
|
internal packet queue while waiting for KEX (#7)
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
In the SSH protocol, clients and servers execute the key exchange to
|
|
generate one-time session keys used for encryption and authentication.
|
|
The key exchange is performed initially after the connection is
|
|
established and then periodically after a configurable amount of data.
|
|
While a key exchange is in progress, we add the received packets to an
|
|
internal queue until we receive SSH_MSG_KEXINIT from the other side.
|
|
This can result in high memory usage if the other party is slow to
|
|
respond to the SSH_MSG_KEXINIT packet, or memory exhaustion if a
|
|
malicious client never responds to an SSH_MSG_KEXINIT packet during a
|
|
large file transfer.
|
|
We now limit the internal queue to 64 packets: this means 2MB with the
|
|
typical 32KB packet size.
|
|
When the internal queue is full we block further writes until the
|
|
pending key exchange is completed or there is a read or write error.
|
|
|
|
Thanks to Yuichi Watanabe for reporting this issue.
|
|
|
|
Fixes: CVE-2025-22869
|
|
Bugs: bsc#1239339
|
|
|
|
Change-Id: I1ce2214cc16e08b838d4bc346c74c72addafaeec
|
|
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/652135
|
|
Reviewed-by: Neal Patel <nealpatel@google.com>
|
|
Auto-Submit: Gopher Robot <gobot@golang.org>
|
|
Reviewed-by: Roland Shoemaker <roland@golang.org>
|
|
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
|
|
|
|
Signed-off-by: Dan Čermák <dcermak@suse.com>
|
|
Signed-off-by: Danish Prakash <contact@danishpraka.sh>
|
|
---
|
|
vendor/golang.org/x/crypto/ssh/handshake.go | 47 ++++++++++++++++-----
|
|
1 file changed, 37 insertions(+), 10 deletions(-)
|
|
|
|
diff --git a/vendor/golang.org/x/crypto/ssh/handshake.go b/vendor/golang.org/x/crypto/ssh/handshake.go
|
|
index 56cdc7c21c3b..a68d20f7f396 100644
|
|
--- a/vendor/golang.org/x/crypto/ssh/handshake.go
|
|
+++ b/vendor/golang.org/x/crypto/ssh/handshake.go
|
|
@@ -25,6 +25,11 @@ const debugHandshake = false
|
|
// quickly.
|
|
const chanSize = 16
|
|
|
|
+// maxPendingPackets sets the maximum number of packets to queue while waiting
|
|
+// for KEX to complete. This limits the total pending data to maxPendingPackets
|
|
+// * maxPacket bytes, which is ~16.8MB.
|
|
+const maxPendingPackets = 64
|
|
+
|
|
// keyingTransport is a packet based transport that supports key
|
|
// changes. It need not be thread-safe. It should pass through
|
|
// msgNewKeys in both directions.
|
|
@@ -73,11 +78,19 @@ type handshakeTransport struct {
|
|
incoming chan []byte
|
|
readError error
|
|
|
|
- mu sync.Mutex
|
|
- writeError error
|
|
- sentInitPacket []byte
|
|
- sentInitMsg *kexInitMsg
|
|
- pendingPackets [][]byte // Used when a key exchange is in progress.
|
|
+ mu sync.Mutex
|
|
+ // Condition for the above mutex. It is used to notify a completed key
|
|
+ // exchange or a write failure. Writes can wait for this condition while a
|
|
+ // key exchange is in progress.
|
|
+ writeCond *sync.Cond
|
|
+ writeError error
|
|
+ sentInitPacket []byte
|
|
+ sentInitMsg *kexInitMsg
|
|
+ // Used to queue writes when a key exchange is in progress. The length is
|
|
+ // limited by pendingPacketsSize. Once full, writes will block until the key
|
|
+ // exchange is completed or an error occurs. If not empty, it is emptied
|
|
+ // all at once when the key exchange is completed in kexLoop.
|
|
+ pendingPackets [][]byte
|
|
writePacketsLeft uint32
|
|
writeBytesLeft int64
|
|
|
|
@@ -133,6 +146,7 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion,
|
|
|
|
config: config,
|
|
}
|
|
+ t.writeCond = sync.NewCond(&t.mu)
|
|
t.resetReadThresholds()
|
|
t.resetWriteThresholds()
|
|
|
|
@@ -259,6 +273,7 @@ func (t *handshakeTransport) recordWriteError(err error) {
|
|
defer t.mu.Unlock()
|
|
if t.writeError == nil && err != nil {
|
|
t.writeError = err
|
|
+ t.writeCond.Broadcast()
|
|
}
|
|
}
|
|
|
|
@@ -362,6 +377,8 @@ write:
|
|
}
|
|
}
|
|
t.pendingPackets = t.pendingPackets[:0]
|
|
+ // Unblock writePacket if waiting for KEX.
|
|
+ t.writeCond.Broadcast()
|
|
t.mu.Unlock()
|
|
}
|
|
|
|
@@ -567,11 +584,20 @@ func (t *handshakeTransport) writePacket(p []byte) error {
|
|
}
|
|
|
|
if t.sentInitMsg != nil {
|
|
- // Copy the packet so the writer can reuse the buffer.
|
|
- cp := make([]byte, len(p))
|
|
- copy(cp, p)
|
|
- t.pendingPackets = append(t.pendingPackets, cp)
|
|
- return nil
|
|
+ if len(t.pendingPackets) < maxPendingPackets {
|
|
+ // Copy the packet so the writer can reuse the buffer.
|
|
+ cp := make([]byte, len(p))
|
|
+ copy(cp, p)
|
|
+ t.pendingPackets = append(t.pendingPackets, cp)
|
|
+ return nil
|
|
+ }
|
|
+ for t.sentInitMsg != nil {
|
|
+ // Block and wait for KEX to complete or an error.
|
|
+ t.writeCond.Wait()
|
|
+ if t.writeError != nil {
|
|
+ return t.writeError
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
if t.writeBytesLeft > 0 {
|
|
@@ -588,6 +614,7 @@ func (t *handshakeTransport) writePacket(p []byte) error {
|
|
|
|
if err := t.pushPacket(p); err != nil {
|
|
t.writeError = err
|
|
+ t.writeCond.Broadcast()
|
|
}
|
|
|
|
return nil
|
|
--
|
|
2.49.0
|
|
|