Skip to content
CVE IDCVE-2026-102823GitHub advisory IDGHSA-47hw-gvq5-r2gm

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

CVE-2026-102823 (GitHub advisory GHSA-47hw-gvq5-r2gm) is a high-severity vulnerability with a CVSS 3.1 base score of 7.5, classed as Improper Input Validation (CWE-20), in russh (crates.io). Affected versions: <= 0.63.0. It is fixed in 0.63.1.

High7.5Fix availableGitHub reviewed

Published 29 Sept 2026Updated 2 Oct 2026

7.5HighCVSS 3.1
EPSS
0.26%higher than 16% of CVEs
Weakness (CWE)
  • CWE-20Improper Input Validation
KEV status
Not in KEV
Published
29 Sept 2026
Updated
2 Oct 2026
Ecosystem
  • crates.io (Rust)
Assigned by (CNA)
GitHub_M

Summary

Source: OSV (GitHub advisory)

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

Technical details

Source: GitHub Advisory Database

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

High7.5Base score from GitHub Advisory Database

Attack Vector AV
Network
Attack Complexity AC
Low
Privileges Required PR
None
User Interaction UI
None
Scope S
Unchanged
Confidentiality C
None
Integrity I
High
Availability A
None

CISA SSVC decision points

Exploitation
poc
Proof-of-concept code is public
Automatable
yes
Attackers can automate this at scale
Technical impact
partial
Limited control of the affected system

CISA Coordinator assessment (SSVC 2.0.3) via CVE.org.

Affected packages

Source: OSV (GitHub advisory)

  • russh
    crates.io (Rust)
    pkg:cargo/russh
    Affected versions
    <= 0.63.0
    Fixed in
    0.63.1

Fix

Source: OSV (GitHub advisory)

Fix available

Upgrade to 0.63.1 or later.

Patch commits

  • 3430fd2Patch commitgithub.com/Eugeny/russh

    correctly handle unexpected channel IDs

    +53 −23 across 3 files

    • russh/src/client/encrypted.rs+34 −2
    • russh/src/server/encrypted.rs+5 −21
    • russh/src/session.rs+14 −0

Release commits

The commits each fixed release tag points at. They usually bump the version rather than contain the fix itself.

  • d3ae702Release commit0.63.1github.com/eugeny/russh

Vulnerability intelligence

References

Credits

Questions about CVE-2026-102823

What is the GHSA ID for CVE-2026-102823?
GitHub tracks CVE-2026-102823 as GHSA-47hw-gvq5-r2gm.
What is the CVSS score of CVE-2026-102823?
CVE-2026-102823 has a CVSS 3.1 base score of 7.5 (High), vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N.
Which versions are affected by CVE-2026-102823?
russh (crates.io): <= 0.63.0.
How do I fix CVE-2026-102823?
Upgrade to 0.63.1 or later. The fix is in commit 3430fd2.
What is the Package URL (PURL) for CVE-2026-102823?
The affected package's PURL is pkg:cargo/russh.
Is CVE-2026-102823 exploited in the wild?
CISA's Known Exploited Vulnerabilities catalog doesn't list CVE-2026-102823. That means no confirmed exploitation has been catalogued, not that it is safe to leave unpatched. CISA's SSVC assessment rates exploitation as "poc". EPSS estimates a 0.26% chance of exploitation in the next 30 days, higher than 16% of CVEs.

Data sources

Upstream records

  • OSV (GitHub advisory)LoadedRaw JSON
  • GitHub Advisory DatabaseLoadedRaw JSON
  • OSV (CVE record)LoadedRaw JSON
  • CVE.orgLoaded
  • CISA KEV catalogLoaded

Fetched 2 days ago (12 Oct 2026, 07:49 UTC). Records refresh automatically after 24 hours.

Where each part of this page came from

Title and description
OSV (GitHub advisory)
Severity score
GitHub Advisory Database
Weakness (CWE)
GitHub Advisory Database
Affected packages
OSV (GitHub advisory)
Fix
OSV (GitHub advisory)
References
Computed by CVEminer
SSVC
CVE.org
KEV status
CISA KEV catalog
EPSS
GitHub Advisory Database

OSV and GitHub Advisory Database content is licensed CC BY 4.0. Values marked as computed or derived were calculated by CVEminer from the upstream data.