GHSA-5xvq-cp9x-6p6r Russh: Pre-auth remote panic via all-zero Curve25519 peer public value (encode_mpint OOB)
A pre-authentication denial-of-service panic in `russh` 0.62.2 (commit
`c4be19f1915c8682f4615c3fd50008512b474491`, current default branch `main` as
of 2026-07-22). An unauthenticated client sends a single `SSH_MSG_KEX_ECDH_INIT`
whose `Q_C` is 32 zero bytes. russh's Curve25519 KEX does not reject the
all-zero peer public value, so `server_dh()` computes the all-zero shared
secret and `compute_exchange_hash()` then calls `encode_mpint(&shared.0, ...)`,
which indexes `s[i]` at `i == s.len()` and **panics** (`index out of bounds:
the len is 32 but the index is 32`) **before host-key signature verification**.
The server KEX task dies on the first KEX message, before authentication.This is reachable with the **default** server configuration
(`Config::default()` → `Preferred::DEFAULT`, whose kex list includes
`curve25519-sha256`) and requires **no caller-supplied parameter**. It is
reproduced end-to-end against the unmodified real russh 0.62.2 library (a real
server + raw TCP client over TCP); the PoC below links the real crate, not a
copied snippet. The defect is still present on `main` HEAD (`v0.62.3`,
2026-07-22) and is not covered by any of the 11 published russh GHSA advisories
(GHSA-cqvm-j2r2-hwpg / CVE-2023-28113 is modp DH group validation, not
Curve25519).Rust bounds-checked panics abort the task safely (no memory corruption / RCE);
the impact is remote **denial of service**.Details`russh/src/kex/curve25519.rs`, `server_dh()` (server path; attacker = client):The server then computes the exchange hash **before** verifying the host-key
signature (`russh/src/server/kex.rs`):`compute_exchange_hash()` calls `encode_mpint(&shared.0, buffer)`, whose
leading-zero skip loop advances `i` to `s.len()` and then indexes `s[i]`
(`russh/src/kex/mod.rs`):On Curve25519, `scalar * MontgomeryPoint([0;32])` yields `MontgomeryPoint([0;32])`
(the identity element), so the all-zero shared secret is attacker-controlled.
RFC 7748 §6 requires implementations to detect and reject all-zero / low-order
peer public values and shared secrets; russh does not. The client path
(`compute_shared_secret`, curve25519.rs:110-142) has the same chain but is
reached only after the server host-key signature is verified, so it requires a
malicious server that can sign its own host key (same root cause, lower
severity).PoCThe PoC is a standalone `examples/` binary that links the **unmodified** real
russh 0.62.2 crate and reproduces over a real TCP connection. It runs an ATTACK
case (all-zero `Q_C` → panic) and a CONTROL case (random `Q_C` → completes kex),
proving the panic is caused specifically by the all-zero value.One-line reproducer`russh/examples/e2e_t13_zero_curve25519.rs`Real captured output (ATTACK, `RUST_BACKTRACE=1`):The backtrace confirms the real in-library call path on a tokio worker,
pre-authentication, before any host-key signature verification.Impact**Remote, pre-authentication denial of service of any russh SSH server using
the default configuration.** A single 37-byte `SSH_MSG_KEX_ECDH_INIT` (`0x1e``0x00000020` + 32 zero bytes) from an unauthenticated client crashes the
server's KEX task before authentication. Because the panic is in an async russh
task it aborts that connection's handler; depending on the embedder's panic
containment it can also tear down the server if the panic is not contained
per-connection.A malicious SSH server can symmetrically crash a russh **client** after
host-key verification by sending an all-zero `Q_S` in
`SSH_MSG_KEX_ECDH_REPLY` (same root cause, lower severity — requires the
server to control its own signed host key).No confidentiality/integrity break is demonstrated. The all-zero shared secret
would itself be a catastrophic key-compromise if russh did not already crash,
but the observed impact is the crash.CVSS`AV:N` — reachable from a remote SSH peer`AC:L` — requires only a 32-byte all-zero `Q_C``PR:N` — pre-authentication`UI:N` — no user interaction`C:N`, `I:N` — no confidentiality or integrity impact demonstrated`A:H` — remote unauthenticated crash of the server KEX taskSuggested fixesReject all-zero / low-order Curve25519 peer public values (RFC 7748 §6) in
`server_dh()` / `compute_shared_secret()`:and harden `encode_mpint` against the all-zero input:Affected versions`russh` **<= 0.62.3** (commit `c4be19f1915c` / current `main` HEAD
`v0.62.3`, 2026-07-22). The bug is still present on `main`; it is not covered
by any of the 11 published russh GHSA advisories. Default `server::Config`
and `client::Config` are affected (no feature flag or opt-in).CreditIndependently reported by [Zhaodl1](https://github.com/Zhaodl1) and the diff/ambidiff security research effort (afldl).
CVSS v3.1 score: 5.3