Update Rust crate russh to 0.62 [SECURITY] - #201
Open
renovate[bot] wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
0.61→0.62Russh: Pre-auth remote panic via all-zero Curve25519 peer public value (encode_mpint OOB)
GHSA-5xvq-cp9x-6p6r
More information
Details
A pre-authentication denial-of-service panic in
russh0.62.2 (commitc4be19f1915c8682f4615c3fd50008512b474491, current default branchmainasof 2026-07-22). An unauthenticated client sends a single
SSH_MSG_KEX_ECDH_INITwhose
Q_Cis 32 zero bytes. russh's Curve25519 KEX does not reject theall-zero peer public value, so
server_dh()computes the all-zero sharedsecret and
compute_exchange_hash()then callsencode_mpint(&shared.0, ...),which indexes
s[i]ati == 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 includescurve25519-sha256) and requires no caller-supplied parameter. It isreproduced 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
mainHEAD (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()callsencode_mpint(&shared.0, buffer), whoseleading-zero skip loop advances
itos.len()and then indexess[i](
russh/src/kex/mod.rs):On Curve25519,
scalar * MontgomeryPoint([0;32])yieldsMontgomeryPoint([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 isreached 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).
PoC
The PoC is a standalone
examples/binary that links the unmodified realrussh 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 (randomQ_C→ completes kex),proving the panic is caused specifically by the all-zero value.
One-line reproducer
russh/examples/e2e_t13_zero_curve25519.rsReal 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(0x1e0x00000020+ 32 zero bytes) from an unauthenticated client crashes theserver'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_SinSSH_MSG_KEX_ECDH_REPLY(same root cause, lower severity — requires theserver 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 peerAC:L— requires only a 32-byte all-zeroQ_CPR:N— pre-authenticationUI:N— no user interactionC:N,I:N— no confidentiality or integrity impact demonstratedA:H— remote unauthenticated crash of the server KEX taskSuggested fixes
Reject all-zero / low-order Curve25519 peer public values (RFC 7748 §6) in
server_dh()/compute_shared_secret():and harden
encode_mpintagainst the all-zero input:Affected versions
russh<= 0.62.3 (commitc4be19f1915c/ currentmainHEADv0.62.3, 2026-07-22). The bug is still present onmain; it is not coveredby any of the 11 published russh GHSA advisories. Default
server::Configand
client::Configare affected (no feature flag or opt-in).Credit
Independently reported by Zhaodl1 and the diff/ambidiff security research effort (afldl).
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Russh: Post-auth remote panic via pty-req with more than 130 terminal-mode records
GHSA-cqjc-rmpq-xprq
More information
Details
Summary
A post-authentication denial-of-service panic in
russh0.62.2 (commitc4be19f1915c8682f4615c3fd50008512b474491, current default branchmainasof 2026-07-22). An authenticated client sends a
pty-reqchannel requestcarrying more than 130 terminal-mode records. The parser uses a fixed
[(Pty::TTY_OP_END, 0); 130]array but increments its counterifor everyvalid record (logging "too many pty codes" without returning), then slices
&modes[0..i]— an out-of-bounds slice that panics (range end index 131 out of range for slice of length 130) before the applicationpty_requesthandler runs.
This is reachable with the default server configuration and the default
crypto config (curve25519-sha256 + chacha20-poly1305), requiring only an
authenticated session channel — no caller-supplied parameter. It is reproduced
end-to-end against the unmodified real russh 0.62.2 library (a real
russh::client+russh::serverover TCP, using the publicChannel::request_pty(...)API); the PoC below links the real crate, not acopied snippet. The defect is still present on
mainHEAD (v0.62.3,2026-07-22) and is not covered by any of the 11 published russh GHSA
advisories (GHSA-4r3c-5hpg-58qr / CVE-2026-48110 is allocation-first string
parsing, not the fixed-array slice overflow; it was fixed in 0.61.0 but this
code path still overflows the fixed array).
Rust bounds-checked panics abort the task safely (no memory corruption / RCE);
the impact is remote denial of service.
Details
russh/src/server/encrypted.rs,pty-reqhandling (lines 1137–1201):Each terminal-mode record is exactly 5 bytes (1-byte opcode + 4-byte value),
and
i += 1runs for every valid record (including repeats of the sameopcode). The
if i < 130write-gate prevents an in-array overflow but thecounter still grows unbounded, and the later
&modes[0..i]slice has nocorresponding bound. SSH packet-size limits do not bound the mode count to
130, so a single normal-sized
pty-reqcan carry hundreds of mode records.The client's own
request_ptyserialization (russh/src/client/session.rs:writes every record with no count cap, so 131 records reach the server as a
legitimate authenticated channel request.
PoC
The PoC is a standalone
examples/binary that links the unmodified realrussh 0.62.2 crate and reproduces over a real TCP connection with the default
crypto config. It runs an ATTACK case (131 mode records → panic) and a CONTROL
case (130 mode records → parses fine), proving the panic is caused
specifically by exceeding the 130-entry array.
One-line reproducer
russh/examples/e2e_c15_pty_modes_panic.rsReal captured output (ATTACK,
RUST_BACKTRACE=1):The panic occurs in russh's own post-auth parser before any application
handler is invoked.
Impact
Remote, post-authentication denial of service of any russh SSH server using
the default configuration. Any authenticated client with a session channel can
crash the russh server task with a single
SSH_MSG_CHANNEL_REQUESTpty-reqcarrying 131+ terminal-mode records (each record is 5 bytes, so 131 records
fit in one normal-sized packet). This is trivially reachable for any legitimate
or compromised SSH user. DoS only — Rust bounds-checked panics abort the task
safely; there is no memory corruption or RCE.
Suggested fix
Cap
iat 130 and reject the request instead of logging and continuing:Affected versions
russh<= 0.62.3 (commitc4be19f1915c/ currentmainHEADv0.62.3, 2026-07-22). The bug is still present onmain; it is not coveredby any of the 11 published russh GHSA advisories. Default
server::Configand
client::Configare affected (no feature flag or opt-in).Credit
Reported by the diff/ambidiff security research effort (afldl). Happy to
coordinate a disclosure timeline; will request a CVE once confirmed.
Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Russh: client wrong-length X25519
clone_from_slicepanic (pre-auth DoS)GHSA-g9hv-x236-4qp3
More information
Details
Summary
A malicious SSH server can crash a
russhclient session with a singlemalformed 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.
Details
Every other kex path in
russhvalidates the peer ephemeral length beforecloning:
Curve25519Kex::server_dh(russh/src/kex/curve25519.rs:61-65) checksif pubkey_len != 32 { return Err(crate::Error::Kex); }beforeclone_from_slice.Only the client-side curve25519
compute_shared_secretis missing the check.This asymmetric validation gap makes the bug easy to miss in code review: a
malicious client cannot panic a
russhserver this way (the server pathchecks the length), but a malicious server can panic a
russhclient.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(spawnwrapstokio::spawn; panic surfaces asJoinError)PoC
A standalone, self-contained Cargo PoC is provided in
vuln_poc/vuln_002_client_wronglen_x25519_panic/in this repo. It installs aglobal panic hook that sets an
AtomicBoolif any panic fires, starts amalicious raw SSH server on
127.0.0.1:0that completes the SSH id andKEXINITexchange, reads the clientKEX_ECDH_INIT, and sendsKEX_ECDH_REPLYwith a 16-byte server ephemeral (instead of 32) and a fakesignature. It then calls
russh::client::connectwithPreferred::kexsetto
curve25519-sha256and a handler that accepts any server key (the checkis never reached because the client panics first) and prints a clear verdict.
Build & run:
cd vuln_poc/vuln_002_client_wronglen_x25519_panic cargo run --releaseExpected output (verdict line, from a successful reproduction):
The malicious payload is the
ffield ofKEX_ECDH_REPLY:The length prefix of
fis4(u32 BE) = 16, followed by 16 bytes. Therusshclient decodes this intoexchange.server_ephemeral(aVec<u8>oflength 16) and passes it to
compute_shared_secret, which panics onclone_from_slice.Impact
What kind of vulnerability: CWE-704 (incorrect type conversion / cast —
clone_from_slicelength mismatch) → deterministic panic → pre-authenticationper-connection Denial-of-Service. The attacker does not need the server's
private key; any network position that can deliver a malformed
KEX_ECDH_REPLY(a rogue server, or a MitM before authentication) suffices.Who is impacted: any deployment that uses
russh::client::connect(orconnect_stream) to connect to an attacker-controlled or MitM-reachable SSHserver, and that negotiates
curve25519-sha256(the default andmost-preferred kex algorithm in
russh). A single malformedKEX_ECDH_REPLYkills the client session; the attack is deterministic andsingle-packet. The panic is isolated to the spawned session task
(
tokio::spawncatches it and surfaces aJoinError), so the embeddingprocess keeps running — the impact is per-connection DoS, not process crash,
unless the embedder installs a custom panic hook that calls
std::process::abort.Workaround: until a fix is released, clients can reduce exposure by
disabling
curve25519-sha256in thePreferred::kexlist and preferring akex algorithm whose peer-ephemeral length is validated (e.g. the ECDH-NIST
or DH/GEX paths). This is a mitigation, not a fix.
Suggested fix (one-line length check, mirrors the existing server-side
server_dhcheck):This makes the client-side
compute_shared_secretconsistent with theexisting server-side
server_dhcheck atrussh/src/kex/curve25519.rs:61-65and with the other kex paths that already validate peer ephemeral lengths.
vuln_poc.zip
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
warp-tech/russh (russh)
v0.62.4Compare Source
Security fixes
Three independent bugs have allowed a client to trigger a panic in the session handler task, thereby crashing their own session.
8912512a7fc1eba7fc1ebMisc
v0.62.3Compare Source
Changes
2e3f1cc: Update more RustCrypto dependencies to stabilized versions (#735) (kpcyrd) [#735]v0.62.2Compare Source
Fixes
6da3f4a: fixed #733 - incorrect first kex guess handling (Eugene)v0.62.1Compare Source
Fixes
6fc20b2: Reply with CHANNEL_CLOSE in server handler per RFC 4254 (#675) (Corey Leavitt) #675v0.62.0Compare Source
Breaking changes
#686 - make channel confirmations truly async
This changes the signature of the
Handler::channel_open_*functions to allow you to make the channel accept/reject decision outside of the main event loop. Instead of immediately returning abool, they take an additionalreply: ChannelOpenHandleargument, which you can move into another async task to confirm or reject the channel later. After the handler function returns, the event loop is immediately unblocked.This also lets you specify the protocol-level rejection reason.
Migrating your existing code:
async fn channel_open_session( &mut self, channel: Channel<Msg>, + reply: server::ChannelOpenHandle, session: &mut Session, - ) -> Result<bool, Self::Error> { + ) -> Result<(), Self::Error> { if (...) { - Ok(false) + reply.reject(ChannelOpenFailure::AdministrativelyProhibited).await; } else { - Ok(true) + reply.accept().await; } + Ok(()) }Changes
New Contributors
Full Changelog: Eugeny/russh@v0.61.2...v0.62.0
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.