{"id":"CVE-2026-102822","aliases":["DEBIAN-CVE-2026-102822","EUVD-2026-89205","GHSA-p8qx-h547-fjw9","UBUNTU-CVE-2026-102822"],"status":"published","title":"russh: negotiating a MAC-requiring block cipher (CTR/CBC) with mac=none causes a slice-index-out-of-range panic","description":"Russh is a Rust SSH client and server library. Prior to 0.63.1, a connection configured to permit mac=none can negotiate it with a MAC-requiring CTR or CBC block cipher because the selection logic validates needs_mac() only when MAC selection fails. A remote peer can then send a packet with a decrypted length of zero, causing russh/src/cipher/mod.rs to shrink the previously read block before indexing buffer.buffer[16..], which panics and terminates the connection task. This issue is fixed in version 0.63.1.","severity":{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00327,"percentile":0.26097,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-129"],"published":"2026-09-29T18:23:28Z","modified":"2026-10-28T02:31:10Z","fixes":[{"ecosystem":"crates.io","package":"russh","fixed_in":["0.63.1"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"rust-russh","fixed_in":["0.64.1-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"crates.io","package":"russh","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"0.63.0"},{"fixed":"0.63.1"}]}]},{"source":"osv","ecosystem":"Debian:14","package":"rust-russh","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.64.1-1"}]}],"versions":["0.57.1-1","0.57.1-2"]},{"source":"osv","ecosystem":"crates.io","package":"russh","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.63.1"}]}]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"rust-russh","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.57.1-2"]}],"products":[{"vendor":"eugeny","product":"russh"}],"references":[{"url":"https://github.com/Eugeny/russh/security/advisories/GHSA-p8qx-h547-fjw9","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/Eugeny/russh/commit/2885385abfee279092a41d80c6cb6ac367353159","tags":["x_refsource_MISC","fix","web","report"]},{"url":"https://github.com/Eugeny/russh/releases/tag/v0.63.1","tags":["x_refsource_MISC","web","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-102822","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-p8qx-h547-fjw9"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/102xxx/CVE-2026-102822.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-102822","tags":["advisory"]},{"url":"https://github.com/Eugeny/russh","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-102822","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-102822","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-102822","title":"russh: negotiating a MAC-requiring block cipher (CTR/CBC) with mac=none causes a slice-index-out-of-range panic","description":"Russh is a Rust SSH client and server library. Prior to 0.63.1, a connection configured to permit mac=none can negotiate it with a MAC-requiring CTR or CBC block cipher because the selection logic validates needs_mac() only when MAC selection fails. A remote peer can then send a packet with a decrypted length of zero, causing russh/src/cipher/mod.rs to shrink the previously read block before indexing buffer.buffer[16..], which panics and terminates the connection task. This issue is fixed in version 0.63.1.","status":"published","published":"2026-09-29T18:23:28.061Z","modified":"2026-09-29T19:53:30.291Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low","assessor":"cna"}],"cwes":["CWE-129"],"updated_at":"2026-11-02T13:38:54.394896Z"}],"euvd":[{"id":"EUVD-2026-89205","description":"Russh is a Rust SSH client and server library. Prior to 0.63.1, a connection configured to permit mac=none can negotiate it with a MAC-requiring CTR or CBC block cipher because the selection logic validates needs_mac() only when MAC selection fails. A remote peer can then send a packet with a decrypted length of zero, causing russh/src/cipher/mod.rs to shrink the previously read block before indexing buffer.buffer[16..], which panics and terminates the connection task. This issue is fixed in version 0.63.1.","status":"published","published":"2026-09-29T18:23:28Z","modified":"2026-09-29T19:53:30Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low"}],"updated_at":"2026-11-02T17:27:51.078545Z"}],"ghsa":[{"id":"GHSA-p8qx-h547-fjw9","title":"russh: negotiating a MAC-requiring block cipher (CTR/CBC) with mac=none causes a slice-index-out-of-range panic","description":"### Summary\n`SshBlockCipher` implementations (AES-CBC, AES-CTR, 3DES-CBC, etc.) report `needs_mac() == true`, meaning they are documented/intended to always be paired with a separate integrity MAC. However, key-exchange negotiation only checks `needs_mac()` inside the *fallback* branch of MAC algorithm selection (used when no common MAC algorithm exists). If both peers' preferred MAC lists simply contain `none` and it is successfully negotiated through the normal selection path, nothing rejects pairing `none` with a cipher that requires a MAC. Once negotiated, a single crafted packet from either peer causes `cipher::read()` to shrink an already-allocated buffer below the number of bytes it is about to index, causing a Rust slice-index-out-of-range panic and killing that connection's task.\n\n### Details\nIn `russh/src/cipher/mod.rs`, `read()` for a block cipher:\n1. Reads `packet_length_to_read_for_block_length()` bytes up front (16 bytes for any `SshBlockCipher`) into `buffer.buffer`.\n2. Decrypts the first block to recover the plaintext packet-length field `len`.\n3. Computes `buffer.len = len + cipher.tag_len()`.\n4. Calls `buffer.buffer.resize(buffer.len + 4, 0)`.\n5. Immediately indexes `buffer.buffer[16..]` (via the constant used for the first block read) to continue decrypting/reading the rest of the packet.\n\nWhen the negotiated MAC is `none`, `tag_len() == 0`. If the attacker (or a MITM holding the session key, or simply the accepting peer testing a hostile client) sends a packet whose *decrypted* length field is `0`, then `buffer.len = 0` and `resize(0 + 4, 0)` **shrinks** the buffer that was already grown to 16 bytes in step 1 down to 4 bytes. The subsequent slice operation `buffer.buffer[16..]` then panics with `range start index 16 out of range for slice of length 4`.\n\nThe file already defines a `MINIMUM_PACKET_LEN` constant, but it is only consulted on the *write*/padding side, never on the read path — so nothing prevents an incoming packet from declaring a length shorter than the bytes already buffered.\n\nNegotiation gap: `negotiation.rs`'s `Select` MAC-selection logic only special-cases `needs_mac()` when negotiation would otherwise *fail* (no common MAC), substituting `none` only if the cipher does not need one. It never re-validates the case where `none` is a common/successfully-negotiated MAC on both sides regardless of what the chosen cipher requires. So an application (or a malicious peer, since negotiation is attacker-influenced on one side) that includes `none` in its own preferred MAC list — while still allowing the default CTR/CBC cipher suite — ends up with an invalid, panic-inducing combination that the library itself should refuse.\n\n### PoC\n1. Configure one side's `Preferred` config to include `mac::NONE` in the MAC list (this is a supported, non-default configuration exposed by the crate's public `Preferred` API — used e.g. for legacy/interop compatibility), while leaving the default cipher list (which includes `aes256-ctr`/`aes256-cbc`) untouched.\n2. Complete a normal key exchange; negotiation lands on `{cipher: aes*-ctr (or -cbc), mac: none}` because `none` is present and preferred/common on both sides, and nothing during negotiation rejects this pairing.\n3. From the peer, send one transport packet whose decrypted packet-length field is `0` (trivial to construct once the session keys are known to that peer, or for the peer that legitimately owns the connection to simply hand-craft, e.g. a modified client for testing).\n4. `cipher::read()` on the receiving side panics: `range start index 16 out of range for slice of length 4`.\n5. Because each connection is handled in its own `tokio::spawn`'ed task (see the per-connection `select!` loop that calls into `cipher::read()`), the panic unwinds only that task by default, but it unconditionally terminates that SSH connection/session — a working, currently-unauthenticated, already-established connection is killed with no attacker interaction beyond the one crafted packet, and the check runs on every inbound packet including pre-auth ones.\n\n\n### Impact\nDenial of service: a remote peer that can influence MAC preference negotiation (or a MITM in possession of the session key) can crash any individual SSH connection/session that ends up negotiating a block cipher together with `mac=none`, with a single crafted packet, pre-authentication. This does not affect the process as a whole (panic is scoped to that connection's task under `panic=unwind`), and does require a non-default configuration that permits `none` as a preferred MAC — hence Low severity.\n\n### Suggested fix\nIn the MAC-selection logic in `negotiation.rs`, reject (or force substitution of) `mac::NONE` whenever the negotiated cipher's `needs_mac()` is `true`, regardless of *how* `none` came to be selected — not only in the \"no common MAC\" fallback branch. Defensively, `cipher::read()` could also refuse to shrink a buffer below the number of bytes already consumed for the length-field block, returning a protocol error instead of resizing blindly.\n\nFor credit/changelog purposes, please use: Yazan Balawneh, Cystack.ps","status":"published","published":"2026-09-30T23:27:30Z","modified":"2026-09-30T23:27:31Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low"}],"cwes":["CWE-129"],"updated_at":"2026-11-02T14:41:23.783773Z"}],"nvd":[{"id":"CVE-2026-102822","description":"Russh is a Rust SSH client and server library. Prior to 0.63.1, a connection configured to permit mac=none can negotiate it with a MAC-requiring CTR or CBC block cipher because the selection logic validates needs_mac() only when MAC selection fails. A remote peer can then send a packet with a decrypted length of zero, causing russh/src/cipher/mod.rs to shrink the previously read block before indexing buffer.buffer[16..], which panics and terminates the connection task. This issue is fixed in version 0.63.1.","status":"published","published":"2026-09-29T19:17:24.19Z","modified":"2026-09-29T20:17:17.673Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low","assessor":"secondary"}],"cwes":["CWE-129"],"updated_at":"2026-11-02T14:12:52.759595Z"}],"osv":[{"id":"CVE-2026-102822","title":"russh: negotiating a MAC-requiring block cipher (CTR/CBC) with mac=none causes a slice-index-out-of-range panic","description":"Russh is a Rust SSH client and server library. Prior to 0.63.1, a connection configured to permit mac=none can negotiate it with a MAC-requiring CTR or CBC block cipher because the selection logic validates needs_mac() only when MAC selection fails. A remote peer can then send a packet with a decrypted length of zero, causing russh/src/cipher/mod.rs to shrink the previously read block before indexing buffer.buffer[16..], which panics and terminates the connection task. This issue is fixed in version 0.63.1.","status":"published","published":"2026-09-29T18:23:28.061Z","modified":"2026-10-28T02:31:10.270459832Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low"}],"cwes":["CWE-129"],"updated_at":"2026-11-02T15:44:53.83521Z"},{"id":"DEBIAN-CVE-2026-102822","description":"Russh is a Rust SSH client and server library. Prior to 0.63.1, a connection configured to permit mac=none can negotiate it with a MAC-requiring CTR or CBC block cipher because the selection logic validates needs_mac() only when MAC selection fails. A remote peer can then send a packet with a decrypted length of zero, causing russh/src/cipher/mod.rs to shrink the previously read block before indexing buffer.buffer[16..], which panics and terminates the connection task. This issue is fixed in version 0.63.1.","status":"published","published":"2026-09-29T19:17:24.19Z","modified":"2026-10-14T16:00:05.980548637Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low"}],"updated_at":"2026-11-02T15:45:48.903138Z"},{"id":"GHSA-p8qx-h547-fjw9","title":"russh: negotiating a MAC-requiring block cipher (CTR/CBC) with mac=none causes a slice-index-out-of-range panic","description":"### Summary\n`SshBlockCipher` implementations (AES-CBC, AES-CTR, 3DES-CBC, etc.) report `needs_mac() == true`, meaning they are documented/intended to always be paired with a separate integrity MAC. However, key-exchange negotiation only checks `needs_mac()` inside the *fallback* branch of MAC algorithm selection (used when no common MAC algorithm exists). If both peers' preferred MAC lists simply contain `none` and it is successfully negotiated through the normal selection path, nothing rejects pairing `none` with a cipher that requires a MAC. Once negotiated, a single crafted packet from either peer causes `cipher::read()` to shrink an already-allocated buffer below the number of bytes it is about to index, causing a Rust slice-index-out-of-range panic and killing that connection's task.\n\n### Details\nIn `russh/src/cipher/mod.rs`, `read()` for a block cipher:\n1. Reads `packet_length_to_read_for_block_length()` bytes up front (16 bytes for any `SshBlockCipher`) into `buffer.buffer`.\n2. Decrypts the first block to recover the plaintext packet-length field `len`.\n3. Computes `buffer.len = len + cipher.tag_len()`.\n4. Calls `buffer.buffer.resize(buffer.len + 4, 0)`.\n5. Immediately indexes `buffer.buffer[16..]` (via the constant used for the first block read) to continue decrypting/reading the rest of the packet.\n\nWhen the negotiated MAC is `none`, `tag_len() == 0`. If the attacker (or a MITM holding the session key, or simply the accepting peer testing a hostile client) sends a packet whose *decrypted* length field is `0`, then `buffer.len = 0` and `resize(0 + 4, 0)` **shrinks** the buffer that was already grown to 16 bytes in step 1 down to 4 bytes. The subsequent slice operation `buffer.buffer[16..]` then panics with `range start index 16 out of range for slice of length 4`.\n\nThe file already defines a `MINIMUM_PACKET_LEN` constant, but it is only consulted on the *write*/padding side, never on the read path — so nothing prevents an incoming packet from declaring a length shorter than the bytes already buffered.\n\nNegotiation gap: `negotiation.rs`'s `Select` MAC-selection logic only special-cases `needs_mac()` when negotiation would otherwise *fail* (no common MAC), substituting `none` only if the cipher does not need one. It never re-validates the case where `none` is a common/successfully-negotiated MAC on both sides regardless of what the chosen cipher requires. So an application (or a malicious peer, since negotiation is attacker-influenced on one side) that includes `none` in its own preferred MAC list — while still allowing the default CTR/CBC cipher suite — ends up with an invalid, panic-inducing combination that the library itself should refuse.\n\n### PoC\n1. Configure one side's `Preferred` config to include `mac::NONE` in the MAC list (this is a supported, non-default configuration exposed by the crate's public `Preferred` API — used e.g. for legacy/interop compatibility), while leaving the default cipher list (which includes `aes256-ctr`/`aes256-cbc`) untouched.\n2. Complete a normal key exchange; negotiation lands on `{cipher: aes*-ctr (or -cbc), mac: none}` because `none` is present and preferred/common on both sides, and nothing during negotiation rejects this pairing.\n3. From the peer, send one transport packet whose decrypted packet-length field is `0` (trivial to construct once the session keys are known to that peer, or for the peer that legitimately owns the connection to simply hand-craft, e.g. a modified client for testing).\n4. `cipher::read()` on the receiving side panics: `range start index 16 out of range for slice of length 4`.\n5. Because each connection is handled in its own `tokio::spawn`'ed task (see the per-connection `select!` loop that calls into `cipher::read()`), the panic unwinds only that task by default, but it unconditionally terminates that SSH connection/session — a working, currently-unauthenticated, already-established connection is killed with no attacker interaction beyond the one crafted packet, and the check runs on every inbound packet including pre-auth ones.\n\n\n### Impact\nDenial of service: a remote peer that can influence MAC preference negotiation (or a MITM in possession of the session key) can crash any individual SSH connection/session that ends up negotiating a block cipher together with `mac=none`, with a single crafted packet, pre-authentication. This does not affect the process as a whole (panic is scoped to that connection's task under `panic=unwind`), and does require a non-default configuration that permits `none` as a preferred MAC — hence Low severity.\n\n### Suggested fix\nIn the MAC-selection logic in `negotiation.rs`, reject (or force substitution of) `mac::NONE` whenever the negotiated cipher's `needs_mac()` is `true`, regardless of *how* `none` came to be selected — not only in the \"no common MAC\" fallback branch. Defensively, `cipher::read()` could also refuse to shrink a buffer below the number of bytes already consumed for the length-field block, returning a protocol error instead of resizing blindly.\n\nFor credit/changelog purposes, please use: Yazan Balawneh, Cystack.ps","status":"published","published":"2026-09-30T23:27:30Z","modified":"2026-09-30T23:45:04.010001283Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low"}],"cwes":["CWE-129"],"updated_at":"2026-11-02T15:46:52.891208Z"},{"id":"UBUNTU-CVE-2026-102822","description":"Russh is a Rust SSH client and server library. Prior to 0.63.1, a connection configured to permit mac=none can negotiate it with a MAC-requiring CTR or CBC block cipher because the selection logic validates needs_mac() only when MAC selection fails. A remote peer can then send a packet with a decrypted length of zero, causing russh/src/cipher/mod.rs to shrink the previously read block before indexing buffer.buffer[16..], which panics and terminates the connection task. This issue is fixed in version 0.63.1.","status":"published","published":"2026-09-29T19:17:00Z","modified":"2026-10-17T12:57:57.036658244Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"low"}],"updated_at":"2026-11-02T16:55:58.519486Z"}]}}
