A malicious SSH server can crash a russh client session with a single
malformed key-exchange reply, causing a pre-authentication Denial-of-Service
before the server host key is verified. The embedding process itself stays
up, but the connection is killed deterministically.
Every other kex path in russh validates the peer ephemeral length before
cloning:
Curve25519Kex::server_dh (russh/src/kex/curve25519.rs:61-65) checks
if pubkey_len != 32 { return Err(crate::Error::Kex); } before
clone_from_slice.Only the client-side curve25519 compute_shared_secret is missing the check.
This asymmetric validation gap makes the bug easy to miss in code review: a
malicious client cannot panic a russh server this way (the server path
checks the length), but a malicious server can panic a russh client.
Incriminated source code (repo-relative paths):
compute_shared_secret: russh/src/kex/curve25519.rs:110-117 (panic at line 113)russh/src/client/kex.rs:266-277 (KEX_ECDH_REPLY → Bytes::decode → compute_shared_secret)russh/src/kex/curve25519.rs:51-88 (server_dh)russh/src/client/mod.rs (connect_stream → russh_util::runtime::spawn)russh-util/src/runtime.rs:37-48 (spawn wraps tokio::spawn; panic surfaces as JoinError)A standalone, self-contained Cargo PoC is provided in
vuln_poc/vuln_002_client_wronglen_x25519_panic/ in this repo. It installs a
global panic hook that sets an AtomicBool if any panic fires, starts a
malicious raw SSH server on 127.0.0.1:0 that completes the SSH id and
KEXINIT exchange, reads the client KEX_ECDH_INIT, and sends
KEX_ECDH_REPLY with a 16-byte server ephemeral (instead of 32) and a fake
signature. It then calls russh::client::connect with...
0.62.4Exploitability
AV:NAC:LPR:NUI:NScope
S:UImpact
C:NI:NA:L5.3/CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L