CVE-2026-116774
russh: peer-declared zero maximum packet size permanently stalls channel writes (silent AsyncWrite hang)
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.60.0 until 0.65.0, channel-open parsing accepts a peer-declared maximum_packet_size of zero and passes it into ChannelTx::poll_writable in russh/src/channels/io/tx.rs. The Channel::data, Channel::make_writer, and Channel::into_stream asynchronous write paths then calculate a writable size of zero and remain pending forever because window adjustments cannot satisfy a static zero packet maximum. An authenticated client can strand server writes, and a malicious server can strand client writes, allowing affected tasks, channels, and connections to accumulate without surfacing an error. This issue is fixed in version 0.65.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
4.3
russh: peer-declared zero maximum packet size permanently stalls channel writes (silent AsyncWrite hang)
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.60.0 until 0.65.0, channel-open parsing accepts a peer-declared maximum_packet_size of zero and passes it into ChannelTx::poll_writable in russh/src/channels/io/tx.rs. The Channel::data, Channel::make_writer, and Channel::into_stream asynchronous write paths then calculate a writable size of zero and remain pending forever because window adjustments cannot satisfy a static zero packet maximum. An authenticated client can strand server writes, and a malicious server can strand client writes, allowing affected tasks, channels, and connections to accumulate without surfacing an error. This issue is fixed in version 0.65.0.
- Record
CVE-2026-116774- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400, CWE-835
- Updated
- 2026-11-02
EUVDEUVD-2026-111389
4.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.60.0 until 0.65.0, channel-open parsing accepts a peer-declared maximum_packet_size of zero and passes it into ChannelTx::poll_writable in russh/src/channels/io/tx.rs. The Channel::data, Channel::make_writer, and Channel::into_stream asynchronous write paths then calculate a writable size of zero and remain pending forever because window adjustments cannot satisfy a static zero packet maximum. An authenticated client can strand server writes, and a malicious server can strand client writes, allowing affected tasks, channels, and connections to accumulate without surfacing an error. This issue is fixed in version 0.65.0.
- Record
EUVD-2026-111389- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-fw5c-8223-6h42
4.3
russh: peer-declared zero maximum packet size permanently stalls channel writes (silent AsyncWrite hang)
Summary
A peer can advertise maximum packet size = 0 in its channel-open message (CHANNEL_OPEN when attacking a server, CHANNEL_OPEN_CONFIRMATION when attacking a client). russh accepts the value without validation and copies it into the channel write state, where it caps the size of every outgoing data chunk. In the AsyncWrite code path (ChannelTx::poll_writable, used by Channel::data(), Channel::make_writer() and Channel::into_stream()), a cap of 0 makes every write future stay Poll::Pending forever: writable = max_packet_size.min(window_size).min(buf_len) is always 0, and no future event can ever raise it (a static maximum packet size of 0 can never be satisfied by any amount of peer-granted window). The write neither completes nor errors, so the application task hangs silently.
Every other write path in russh already treats max_packet_size == 0 as an error (Error::Inconsistent): Channel::reserve_writable_chunk (russh/src/channels/mod.rs:363-365) and the session packetizers (russh/src/session.rs:489-492 and 549-552). The AsyncWrite path is the only one that lacks the guard - it contradicts the crate's own invariant.
Root cause
(1) Peer value accepted without validation:
russh/src/parsing.rs:44(OpenChannelMessage::parse):let maxpacket = map_err!(u32::decode(r))?;stored asrecipient_maximum_packet_size(:25-30); no range check. Same forChannelOpenConfirmation::decodeatrussh/src/parsing.rs:194-207.- The only packet-size validation anywhere is an upper bound on the local config that merely logs (
russh/src/client/mod.rs:1189-1194,russh/src/server/mod.rs:912-917). Nothing validates the peer's value - 0 passes silently.
(2) The value flows into the channel write state verbatim:
- Server:
russh/src/server/encrypted.rs:1877-1900-ChannelParams { recipient_maximum_packet_size: msg.recipient_maximum_packet_size, .. }, thenChannel::new(..., channel_params.recipient_maximum_packet_size, ...). - Client:
russh/src/client/encrypted.rs:471-488-parameters.confirm(&msg)(russh/src/lib_inner.rs:519-523) andChannelMsg::Open { max_packet_size: msg.maximum_packet_size, .. }, consumed bywait_channel_confirmation(russh/src/client/mod.rs:698-722), which buildsChannelWriteHalf { max_packet_size, .. }. - The write half hands the value to the writer:
make_writer_ext(russh/src/channels/mod.rs:433-441) andinto_stream(russh/src/channels/mod.rs:661-670) passself.max_packet_sizetoChannelTx::new.
(3) The hang - russh/src/channels/io/tx.rs:82-108 (current v0.64.1, byte-identical in v0.60.0):
fn poll_writable(&mut self, cx: &mut Context<'_>, buf_len: usize) -> Poll<NonZeroUsize> {
...
let writable = (self.max_packet_size).min(*window_size).min(buf_len as u32) as usize;
match NonZeroUsize::try_from(writable) {
Ok(w) => {
*window_size -= writable as u32;
if *window_size > 0 {
self.notify.notify_one();
}
Poll::Ready(w)
}
Err(_) => {
drop(window_size);
ready!(self.window_size_notication.poll_unpin(cx));
self.window_size_notication = WatchNotification::new(Arc::clone(&self.notify));
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
With max_packet_size == 0, writable is always 0 regardless of window_size and buf_len; the task parks on the window notification, gets re-polled on every CHANNEL_WINDOW_ADJUST, recomputes 0, and parks again - forever.
(4) The guard exists everywhere else:
// russh/src/channels/mod.rs:363-365 (used by send_bytes -> data_bytes / extended_data_bytes)
if self.max_packet_size == 0 {
return Err(Error::Inconsistent);
}
// russh/src/session.rs:490-492 and :550-552 (packetizer)
if max_packet_size == 0 {
return Err(crate::Error::Inconsistent);
}
Note that Channel::data(reader)…
- Record
GHSA-fw5c-8223-6h42- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400, CWE-835
- Updated
- 2026-11-02
NVD
4.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.60.0 until 0.65.0, channel-open parsing accepts a peer-declared maximum_packet_size of zero and passes it into ChannelTx::poll_writable in russh/src/channels/io/tx.rs. The Channel::data, Channel::make_writer, and Channel::into_stream asynchronous write paths then calculate a writable size of zero and remain pending forever because window adjustments cannot satisfy a static zero packet maximum. An authenticated client can strand server writes, and a malicious server can strand client writes, allowing affected tasks, channels, and connections to accumulate without surfacing an error. This issue is fixed in version 0.65.0.
- Record
CVE-2026-116774- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400, CWE-835
- Updated
- 2026-11-02
OSV
4.3
russh: peer-declared zero maximum packet size permanently stalls channel writes (silent AsyncWrite hang)
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.60.0 until 0.65.0, channel-open parsing accepts a peer-declared maximum_packet_size of zero and passes it into ChannelTx::poll_writable in russh/src/channels/io/tx.rs. The Channel::data, Channel::make_writer, and Channel::into_stream asynchronous write paths then calculate a writable size of zero and remain pending forever because window adjustments cannot satisfy a static zero packet maximum. An authenticated client can strand server writes, and a malicious server can strand client writes, allowing affected tasks, channels, and connections to accumulate without surfacing an error. This issue is fixed in version 0.65.0.
- Record
CVE-2026-116774- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400, CWE-835
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-116774
4.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.60.0 until 0.65.0, channel-open parsing accepts a peer-declared maximum_packet_size of zero and passes it into ChannelTx::poll_writable in russh/src/channels/io/tx.rs. The Channel::data, Channel::make_writer, and Channel::into_stream asynchronous write paths then calculate a writable size of zero and remain pending forever because window adjustments cannot satisfy a static zero packet maximum. An authenticated client can strand server writes, and a malicious server can strand client writes, allowing affected tasks, channels, and connections to accumulate without surfacing an error. This issue is fixed in version 0.65.0.
- Record
DEBIAN-CVE-2026-116774- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Updated
- 2026-11-02
OSVGHSA-fw5c-8223-6h42
4.3
russh: peer-declared zero maximum packet size permanently stalls channel writes (silent AsyncWrite hang)
Summary
A peer can advertise maximum packet size = 0 in its channel-open message (CHANNEL_OPEN when attacking a server, CHANNEL_OPEN_CONFIRMATION when attacking a client). russh accepts the value without validation and copies it into the channel write state, where it caps the size of every outgoing data chunk. In the AsyncWrite code path (ChannelTx::poll_writable, used by Channel::data(), Channel::make_writer() and Channel::into_stream()), a cap of 0 makes every write future stay Poll::Pending forever: writable = max_packet_size.min(window_size).min(buf_len) is always 0, and no future event can ever raise it (a static maximum packet size of 0 can never be satisfied by any amount of peer-granted window). The write neither completes nor errors, so the application task hangs silently.
Every other write path in russh already treats max_packet_size == 0 as an error (Error::Inconsistent): Channel::reserve_writable_chunk (russh/src/channels/mod.rs:363-365) and the session packetizers (russh/src/session.rs:489-492 and 549-552). The AsyncWrite path is the only one that lacks the guard - it contradicts the crate's own invariant.
Root cause
(1) Peer value accepted without validation:
russh/src/parsing.rs:44(OpenChannelMessage::parse):let maxpacket = map_err!(u32::decode(r))?;stored asrecipient_maximum_packet_size(:25-30); no range check. Same forChannelOpenConfirmation::decodeatrussh/src/parsing.rs:194-207.- The only packet-size validation anywhere is an upper bound on the local config that merely logs (
russh/src/client/mod.rs:1189-1194,russh/src/server/mod.rs:912-917). Nothing validates the peer's value - 0 passes silently.
(2) The value flows into the channel write state verbatim:
- Server:
russh/src/server/encrypted.rs:1877-1900-ChannelParams { recipient_maximum_packet_size: msg.recipient_maximum_packet_size, .. }, thenChannel::new(..., channel_params.recipient_maximum_packet_size, ...). - Client:
russh/src/client/encrypted.rs:471-488-parameters.confirm(&msg)(russh/src/lib_inner.rs:519-523) andChannelMsg::Open { max_packet_size: msg.maximum_packet_size, .. }, consumed bywait_channel_confirmation(russh/src/client/mod.rs:698-722), which buildsChannelWriteHalf { max_packet_size, .. }. - The write half hands the value to the writer:
make_writer_ext(russh/src/channels/mod.rs:433-441) andinto_stream(russh/src/channels/mod.rs:661-670) passself.max_packet_sizetoChannelTx::new.
(3) The hang - russh/src/channels/io/tx.rs:82-108 (current v0.64.1, byte-identical in v0.60.0):
fn poll_writable(&mut self, cx: &mut Context<'_>, buf_len: usize) -> Poll<NonZeroUsize> {
...
let writable = (self.max_packet_size).min(*window_size).min(buf_len as u32) as usize;
match NonZeroUsize::try_from(writable) {
Ok(w) => {
*window_size -= writable as u32;
if *window_size > 0 {
self.notify.notify_one();
}
Poll::Ready(w)
}
Err(_) => {
drop(window_size);
ready!(self.window_size_notication.poll_unpin(cx));
self.window_size_notication = WatchNotification::new(Arc::clone(&self.notify));
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
With max_packet_size == 0, writable is always 0 regardless of window_size and buf_len; the task parks on the window notification, gets re-polled on every CHANNEL_WINDOW_ADJUST, recomputes 0, and parks again - forever.
(4) The guard exists everywhere else:
// russh/src/channels/mod.rs:363-365 (used by send_bytes -> data_bytes / extended_data_bytes)
if self.max_packet_size == 0 {
return Err(Error::Inconsistent);
}
// russh/src/session.rs:490-492 and :550-552 (packetizer)
if max_packet_size == 0 {
return Err(crate::Error::Inconsistent);
}
Note that Channel::data(reader)…
- Record
GHSA-fw5c-8223-6h42- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400, CWE-835
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-116774
4.3
russh is a Rust SSH client and server library that includes an SSH agent protocol implementation. From 0.60.0 until 0.65.0, channel-open parsing accepts a peer-declared maximum_packet_size of zero and passes it into ChannelTx::poll_writable in russh/src/channels/io/tx.rs. The Channel::data, Channel::make_writer, and Channel::into_stream asynchronous write paths then calculate a writable size of zero and remain pending forever because window adjustments cannot satisfy a static zero packet maximum. An authenticated client can strand server writes, and a malicious server can strand client writes, allowing affected tasks, channels, and connections to accumulate without surfacing an error. This issue is fixed in version 0.65.0.
- Record
UBUNTU-CVE-2026-116774- CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
russhcrates.io |
0.60.0 to 0.64.1 | 0.65 |
rust-russhDebian:14 |
All versions, plus listed versions 0.57.1-10.57.1-20.64.1-10.64.1-2 |
None known |
russhcrates.io |
0.60.0 up to 0.65 | 0.65 |
rust-russhUbuntu:26.10 |
All versions, plus listed versions 0.57.1-2 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-116774 is a medium vulnerability (CVSS 4.3) in Eugeny Russh, published October 27, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.26%, the 17th percentile. Fixed in russh 0.65.
References
github.com/Eugeny/russh/security/advisories/GHSA-fw5c-8223-6h42
github.com/Eugeny/russh/commit/39cb9dbaa37caf555d34c6c22efa3bbbff9e186c
github.com/Eugeny/russh/releases/tag/v0.65.0
nvd.nist.gov/vuln/detail/CVE-2026-116774
github.com/advisories/GHSA-fw5c-8223-6h42
github.com/CVEProject/cvelistV5/tree/main/cves/2026/116xxx/CVE-2026-116774.json
Left out here: the rest of 2 source descriptions. The JSON record has them.