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

russh client invokes channel-scoped Handler callbacks for channel IDs it never opened, letting a malicious SSH server spoof exit-status/close events and panic clients.

- Payout: Research
- Severity: high
- Program: russh
- Found by: sonicnew
- Disclosed: 2026-09-30
- CVE: CVE-2026-102823
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-47hw-gvq5-r2gm
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-47hw-gvq5-r2gm
- Tags: area:devices domain:network impact:dos impact:integrity kind:research platform:ghsa sev:high tech:rust technique:incomplete-fix technique:source-review vuln:business-logic vuln:dos

**Target:** SSH client library (russh client/encrypted.rs authenticated message dispatch)
**Root cause:** The server-side fix for CVE-2026-68930 added an is_established_channel() gate before Handler callbacks, but the identical check was never added to client_read_authenticated; channel-scoped messages only optionally forward to the per-channel mpsc sender and then unconditionally call the public Handler methods.
**Steps:**
1. Authenticate to a malicious/compromised SSH server using russh as client.
2. Predict next channel ID: Session::new_channel_id() allocates sequentially from 1.
3. Server sends `SSH_MSG_CHANNEL_REQUEST` with exit-status/exit-signal, or `SSH_MSG_CHANNEL_DATA`, `SSH_MSG_CHANNEL_CLOSE`, `SSH_MSG_CHANNEL_SUCCESS`/`FAILURE`, `SSH_MSG_CHANNEL_OPEN_FAILURE` for an arbitrary or not-yet-opened channel ID.
4. Observe client Handler callbacks (client.exit_status, client.data, client.channel_close, client.channel_success) fire despite no matching channel in self.channels.
**How found:** Noticed the server-side fix for CVE-2026-68930 gated callbacks on is_established_channel(), then checked the client path and found only the mpsc forward was conditional while Handler calls were unconditional; corroborated by the unused Error::WrongChannel variant documented as 'Message received/sent on unopened channel'.
**Exploitation:** A hostile, MITM'd, or rogue jump-host SSH server injects channel-scoped messages for predicted/never-opened/closed channel IDs after auth. Applications keying state by ChannelId (HashMaps, exit-code trackers, completion futures) receive callbacks for unknown IDs, causing panics or forged exit-status 0 / premature channel_close that desynchronizes command-completion bookkeeping.
**Escalation:** Chaining spoofed exit-status with premature close lets automation believe a not-yet-run remote command succeeded, undermining CI/orchestration integrity.
**Insight:** When a patch adds a validation gate on one side of a symmetric protocol implementation, diff the mirror path (server vs client) for the same message dispatch; dead error variants like WrongChannel signal a check that was intended but never wired up.

