Vulnerability GHSA-47hw-gvq5-r2gm

High Risk
HIGH RISK
CVSS Score: 7.5
Score Range: 7.0–8.9
High severity vulnerabilities (CVSS 7.0–8.9). Serious vulnerabilities that should be prioritized soon after critical fixes.
7 hours ago
September 30, 2026 at 11:27 PM UTC
russh: Client-side channel-scoped Handler callbacks fire for channel IDs the client never opened
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 and 0.63.0
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 and 0.63.0

Summary

russh: Client-side channel-scoped Handler callbacks fire for channel IDs the client never opened

Details

Summary

CVE-2026-68930 was fixed by adding Session::is_established_channel() in russh/src/server/encrypted.rs, which gates every channel-scoped SERVER-side message (CHANNEL_REQUEST, CHANNEL_DATA, CHANNEL_EOF, CHANNEL_CLOSE, CHANNEL_WINDOW_ADJUST, CHANNEL_EXTENDED_DATA) on enc.channels.get(&channel).is_some_and(|c| c.confirmed) before invoking any Handler callback. The identical validation was never added to the CLIENT side (russh/src/client/encrypted.rs), which processes channel-scoped messages sent by the SSH SERVER once the client has authenticated.

Details

In client_read_authenticated (russh/src/client/encrypted.rs, ~lines 431-757), for CHANNEL_DATA, CHANNEL_EXTENDED_DATA, CHANNEL_EOF, CHANNEL_CLOSE, CHANNEL_OPEN_FAILURE, CHANNEL_SUCCESS, CHANNEL_FAILURE, and the CHANNEL_REQUEST sub-types exit-status/exit-signal/xon-xoff, the code only optionally forwards the event to the internal per-channel mpsc sender via if let Some(chan) = self.channels.get(&channel_num) { ... } (a no-op if the channel is unknown), but then unconditionally calls the corresponding public Handler trait method (client.data(...), client.exit_status(...), client.channel_close(...), client.channel_success(...), etc.) regardless of whether channel_num corresponds to any channel the client ever opened or that was ever confirmed. Only CHANNEL_OPEN_CONFIRMATION (closes the connection with Error::Inconsistent if unknown) and CHANNEL_WINDOW_ADJUST (returns early with Ok(()) if unknown) correctly validate channel existence before acting.

Corroborating evidence this check was intended but never wired up: crate::Error defines a dedicated WrongChannel variant documented as "Message received/sent on unopened channel" (russh/src/lib_inner.rs, ~line 144-146), yet a repo-wide search shows this variant is never constructed or returned anywhere in the codebase — dead code left over from (or intended for) exactly this validation.

Because Session::new_channel_id() (russh/src/session.rs, ~line 708) allocates channel IDs sequentially starting at 1, a malicious or compromised SSH server can trivially predict the ID of the client's next channel and inject spoofed lifecycle events for it before or interleaved with the real channel-open exchange, or replay events for already-closed channel IDs.

PoC

Many real-world consumers of russh-as-a-client (deployment/orchestration tools, CI runners connecting to build/bastion hosts, git-over-ssh style tooling, database/tunnel clients) implement the client::Handler trait directly and key their own state (e.g. HashMap<ChannelId, CommandState>, exit-code trackers, per-channel byte counters, completion futures) off the channel IDs the library hands them, trusting the documented contract that events like "The remote process has exited" (exit_status) or "Called when the server closes a channel" (channel_close) only fire for a channel the application itself opened.

A malicious, MITM'd (via a compromised/rogue jump host the client is configured to trust), or simply hostile SSH server can send SSH_MSG_CHANNEL_REQUEST (exit-status/exit-signal), SSH_MSG_CHANNEL_DATA, SSH_MSG_CHANNEL_CLOSE, SSH_MSG_CHANNEL_SUCCESS/FAILURE, or SSH_MSG_CHANNEL_OPEN_FAILURE for an arbitrary/predicted/never-opened channel ID at any point after authentication completes. Because the library invokes the Handler callback unconditionally, this reaches application code with an ID it never registered.

Impact

(1) A reliable, purely protocol-level trigger for an application panic/DoS in any client that indexes per-channel state by ChannelId without itself re-checking channel validity — the exact class of bug CVE-2026-68930 fixed server-side; and (2) lets the server spoof exit-status/exit-signal/close/success/failure notifications for a channel the client has not yet opened or has already released, desynchronizing the client's command-completion bookkeeping (e.g. reporting a forged exit code 0 for a not-yet-run remote command, or a premature channel_close before real output/exit-status has arrived) — a business-logic-level integrity violation of the SSH channel lifecycle that automation built on russh implicitly relies on.

Suggested fix: add the same is_established_channel()-style gate already used in server/encrypted.rs to client/encrypted.rs's client_read_authenticated, checking self.channels.get(&channel_num) before invoking any Handler callback (not just the mpsc forward), for every channel-scoped message type.

For credit/changelog purposes, please use: Yazan Balawneh, Cystack.ps

Related Vulnerabilities

Other vulnerabilities affecting the same packages

Medium Risk
7 hours ago
Russh: Unbounded memory exhaustion via CHANNEL_OPEN flood during a client-stalled rekey
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 and 0.63.0 - 0.63.1 GHSA-35g8-35p8-c8fw
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 and 0.63.0 - 0.63.1 GHSA-35g8-35p8-c8fw
Low Risk
7 hours ago
russh: negotiating a MAC-requiring block cipher (CTR/CBC) with mac=none causes a slice-index-out-of-range panic
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 and 0.63.0 GHSA-p8qx-h547-fjw9
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 and 0.63.0 GHSA-p8qx-h547-fjw9
Medium Risk
7 hours ago
Russh: Missing X25519 zero-point validation in hybrid ML-KEM key exchange
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 GHSA-w3jg-pjxf-73p4
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.7 GHSA-w3jg-pjxf-73p4
Low Risk
7 hours ago
Russh: Configured server auth-attempt cap is not enforced in the USERAUTH_REQUEST runtime path
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.5 GHSA-g6xm-f9xp-qq35
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.5 GHSA-g6xm-f9xp-qq35
Medium Risk
1 month ago
Russh: Channel-scoped server callbacks can be reached without an open channel
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.4 GHSA-m65r-rprj-r5rg
0.34.0 and 0.36.0 - 0.36.2 and 0.37.1 and 0.38.0 and 0.39.0 - 0.40.2 and 0.42.0 and 0.43.0 and 0.44.0 - 0.45.0 and 0.46.0 and 0.48.0 - 0.49.2 and 0.50.0 - 0.50.4 and 0.51.0 - 0.51.1 and 0.52.0 - 0.52.1 and 0.53.0 - 0.62.4 GHSA-m65r-rprj-r5rg
View all vulnerabilities for these packages

Impacted packages

Timeline

Published
7 hours ago
September 30, 2026 at 11:27 PM UTC
Fixed (0.63.1)
1 month ago
August 23, 2026 at 06:16 PM UTC
Last Modified
7 hours ago
September 30, 2026 at 11:45 PM UTC