Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 275/275 feeds/APIs · 469/469 docs · 8/9 advisories · checked 09:28 UTC Customize Public modeDiscuss ChangeIntel on Discord

Russh: Unchecked CryptoVec allocation and growth handling is reachable from local agent inputs in current russh releases and from remote SSH traffic in historical pre-0.58.0 releases

Collected exploitation and prediction evidence

Risk source coverage

CISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.

No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.

cve-program · CVE-2026-46673

Published

Russh is a Rust SSH client & server library. Prior to version 0.60.3, CryptoVec used unchecked capacity growth, unchecked length arithmetic, and unsafe allocation/locking paths. In current russh releases, local SSH agent peers could still feed attacker-controlled frame lengths into buffer growth before validation. In older russh releases before 0.58.0, remote SSH traffic also reached CryptoVec through transport and compression buffers. This issue has been patched in version 0.60.3.

Source published
10 Jun 2026 20:16:28 UTC
Source modified
11 Jun 2026 14:33:14 UTC
Observed here
23 Oct 2026 15:48:27 UTC
Source terms
CVE Program Terms of Use

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.

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

euvd · EUVD-2026-36124

Published

Russh is a Rust SSH client & server library. Prior to version 0.60.3, CryptoVec used unchecked capacity growth, unchecked length arithmetic, and unsafe allocation/locking paths. In current russh releases, local SSH agent peers could still feed attacker-controlled frame lengths into buffer growth before validation. In older russh releases before 0.58.0, remote SSH traffic also reached CryptoVec through transport and compression buffers. This issue has been patched in version 0.60.3.

EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.

Original publisher date fields

datePublished: Jun 10, 2026, 8:16:28 PM

dateUpdated: Jun 11, 2026, 2:33:14 PM

Source published
Jun 10, 2026, 8:16:28 PM (timezone not stated)
Source modified
Jun 11, 2026, 2:33:14 PM (timezone not stated)
Observed here
9 Nov 2026 11:12:40 UTC
Source terms
ENISA EUVD · source terms and attribution apply

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.

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

nvd · CVE-2026-46673

Published

Russh is a Rust SSH client & server library. Prior to version 0.60.3, CryptoVec used unchecked capacity growth, unchecked length arithmetic, and unsafe allocation/locking paths. In current russh releases, local SSH agent peers could still feed attacker-controlled frame lengths into buffer growth before validation. In older russh releases before 0.58.0, remote SSH traffic also reached CryptoVec through transport and compression buffers. This issue has been patched in version 0.60.3.

NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.

Source analysis status: Deferred

Weakness classifications: CWE-770

Original publisher date fields

published: 2026-06-10T22:17:00.167

lastModified: 2026-07-23T09:10:00.113

Source published
10 Jun 2026 22:17:00 UTC
Source modified
23 Jul 2026 09:10:00 UTC
Observed here
25 Oct 2026 12:17:20 UTC
Source terms
NIST NVD usage terms; underlying source attribution retained

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS 3.1 · [email protected] · Secondary · 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

osv · GHSA-g9f8-wqj9-fjw5

Published

### Title Unchecked `CryptoVec` allocation and growth handling was reachable from local agent inputs in current `russh` releases and from remote SSH traffic in historical pre-`0.58.0` releases ### Summary `CryptoVec` used unchecked capacity growth, unchecked length arithmetic, and unsafe allocation/locking paths. In current `russh` releases, local SSH agent peers could still feed attacker-controlled frame lengths into buffer growth before validation. In older `russh` releases before `0.58.0`, remote SSH traffic also reached `CryptoVec` through transport and compression buffers. ### Details The underlying unsafe paths were in `CryptoVec`: - `cryptovec/src/cryptovec.rs` - unchecked capacity growth - unchecked length arithmetic in growth callers - raw allocation and reallocation paths coupled to those sizes - `cryptovec/src/platform/unix.rs` - `mlock` / `munlock` previously accepted zero-length calls and performed null-pointer validation inside the `unsafe` OS-call path There are two relevant reachability stories: 1. current local reachability in `russh` - `russh/src/keys/agent/client.rs` - `AgentClient::read_response()` read a peer-supplied `u32` length and then resized `self.buf` to that value before reading the payload - `russh/src/keys/agent/server.rs` - `Connection::run()` read a peer-supplied `u32` length and then resized `self.buf` to that value before reading the payload This is the path that still existed in current `0.60.x` releases before the fix, although by then those buffers were no longer `CryptoVec`. 2. historical remote reachability in older `russh` - before commit `712e32b` (first released in `v0.58.0`), non-secret transport and compression buffers in `russh` still used `CryptoVec` - I verified this in a detached pre-`712e32b` worktree by adding and running: - `cipher::tests::remote_packet_length_grows_transport_cryptovec_buffer` - `compression::tests::remote_compressed_payload_expands_cryptovec_output` - those tests show that remote SSH traffic could grow `CryptoVec` through: - transport packet reads - zlib decompression output Also added a constrained-memory reproduction in that historical worktree: - `compression::tests::remote_compressed_payload_can_crash_under_memory_limit` That test re-execs the test binary under `prlimit --as=134217728`, decompresses a highly compressible payload that expands to `96 MiB`, and reliably aborts in the old Unix `CryptoVec` path when `NonNull::new_unchecked()` receives a null pointer after allocation failure. The prepared patch does two things: 1. hardens `CryptoVec` itself - checked capacity growth - checked length arithmetic - immediate allocation-failure handling - zero-length `mlock` / `munlock` no-ops - explicit null-pointer validation before entering the Unix `unsafe` locking calls 2. hardens the real untrusted-input path - caps agent frame lengths at `256 * 1024` on both client and server before resizing buffers This cap matches OpenSSH’s agent framing guardrail. ### PoC The following end-to-end tests demonstrate the real untrusted-input path by feeding oversized peer-controlled agent frame lengths into the public client and server flows and asserting that they are rejected before buffer growth. Client-side agent reply path: ```rust #[test] fn oversized_agent_response_is_rejected_before_allocation() -> std::io::Result<()> { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build()?; runtime.block_on(async { let (mut writer, reader) = tokio::io::duplex(64); let server = tokio::spawn(async move { let mut frame = [0u8; 4]; writer.read_exact(&mut frame).await?; let len = BigEndian::read_u32(&frame) as usize; let mut body = vec![0; len]; writer.read_exact(&mut body).await?; BigEndian::write_u32(&mut frame, (MAX_AGENT_FRAME_LEN + 1) as u32); writer.write_all(&frame).await?; Ok::<(), std::io::Error>(()) }); let mut client = AgentClient::connect(reader); let err = client.request_identities().await.unwrap_err(); assert!(matches!(err, Error::AgentProtocolError)); server.await.expect("server task")?; Ok::<(), std::io::Error>(()) })?; Ok(()) } ``` Server-side agent request path: ```rust #[test] fn oversized_agent_request_is_rejected_before_allocation() -> std::io::Result<()> { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build()?; runtime.block_on(async { let (server, mut client) = tokio::io::duplex(64); let connection = Connection { lock: Lock(std::sync::Arc::new(std::sync::RwLock::new(crate::CryptoVec::new()))), keys: KeyStore(std::sync::Arc::new(std::sync::RwLock::new( std::collections::HashMap::new(), ))), agent: Some(()), s: server, buf: Vec::new(), }; let server = tokio::spawn(async move { connection.run().await }); let mut frame = [0u8; 4]; BigEndian::write_u32(&mut frame, (MAX_AGENT_FRAME_LEN + 1) as u32); client.write_all(&frame).await?; drop(client); let err = server.await.expect("server task").unwrap_err(); assert!(matches!(err, Error::AgentProtocolError)); Ok::<(), std::io::Error>(()) })?; Ok(()) } ``` These tests pass on the fixed branch and fail on unfixed `v0.60.2`, where oversized agent frame lengths are not rejected at the framing boundary. For historical `russh < 0.58.0`, I also verified remote reachability into `CryptoVec` in a detached pre-`712e32b` worktree (`91d431d`, package version `0.57.1`). Transport packet read path: ```rust #[test] fn remote_packet_length_grows_transport_cryptovec_buffer() -> std::io::Result<()> { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build()?; runtime.block_on(async { let packet_len = MAXIMUM_PACKET_LEN; let (mut writer, mut reader) = tokio::io::duplex(packet_len + 4); let writer_task = tokio::spawn(async move { let mut packet = vec![0u8; packet_len + 4]; packet[..4].copy_from_slice(&(packet_len as u32).to_be_bytes()); writer.write_all(&packet).await?; Ok::<(), std::io::Error>(()) }); let mut buffer = SSHBuffer::new(); let mut cipher = clear::Key; let n = read(&mut reader, &mut buffer, &mut cipher).await.unwrap(); assert_eq!(n, packet_len + 4); assert_eq!(buffer.buffer.len(), packet_len + 4); assert_eq!(&buffer.buffer[..4], &(packet_len as u32).to_be_bytes()); writer_task.await.expect("writer task")?; Ok::<(), std::io::Error>(()) })?; Ok(()) } ``` Compression growth path: ```rust #[test] fn remote_compressed_payload_expands_cryptovec_output() { let payload = vec![b'A'; 64 * 1024]; let compression = Compression::new(&ZLIB); let mut compressor = Compress::None; let mut decompressor = Decompress::None; compression.init_compress(&mut compressor); compression.init_decompress(&mut decompressor); let mut compressed = CryptoVec::new(); let encoded = compressor .compress(&payload, &mut compressed) .expect("compress") .to_vec(); let mut output = CryptoVec::new(); let decoded = decompressor .decompress(&encoded, &mut output) .expect("decompress"); assert_eq!(decoded.len(), payload.len()); assert_eq!(decoded, payload.as_slice()); assert!(encoded.len() < output.len()); } ``` Constrained-memory crash reproduction for the historical remote compression path: ```rust #[test] fn remote_compressed_payload_can_crash_under_memory_limit() { const CHILD_ENV: &str = "RUSSH_REMOTE_COMPRESS_CRASH_CHILD"; if std::env::var_os(CHILD_ENV).is_some() { let payload = vec![b'A'; 96 * 1024 * 1024]; let compression = Compression::new(&ZLIB); let mut compressor = Compress::None; let mut decompressor = Decompress::None; compression.init_compress(&mut compressor); compression.init_decompress(&mut decompressor); let mut compressed = CryptoVec::new(); let encoded = compressor .compress(&payload, &mut compressed) .expect("compress") .to_vec(); let mut output = CryptoVec::new(); let decoded = decompressor .decompress(&encoded, &mut output) .expect("decompress"); assert_eq!(decoded.len(), payload.len()); return; } let exe = std::env::current_exe().expect("current exe"); let status = Command::new("prlimit") .args([ "--as=134217728", "--", exe.to_str().expect("utf8 exe path"), "--exact", "compression::tests::remote_compressed_payload_can_crash_under_memory_limit", "--nocapture", ]) .env(CHILD_ENV, "1") .status() .expect("spawn child"); assert!( !status.success(), "expected child to fail under constrained address space" ); } ``` On that historical worktree, the constrained-memory child aborts in the old Unix `CryptoVec` path with: ```text unsafe precondition(s) violated: NonNull::new_unchecked requires that the pointer is non-null thread caused non-unwinding panic. aborting. ``` To run the reproduced checks: ```bash cargo test -p russh oversized_agent_response_is_rejected_before_allocation -- --nocapture cargo test -p russh oversized_agent_request_is_rejected_before_allocation -- --nocapture cargo test -p russh-cryptovec ``` Historical pre-`0.58.0` checks were run from the detached `91d431d` worktree with: ```bash cargo test --offline -p russh remote_packet_length_grows_transport_cryptovec_buffer -- --nocapture cargo test --offline -p russh remote_compressed_payload_expands_cryptovec_output -- --nocapture cargo test --offline -p russh remote_compressed_payload_can_crash_under_memory_limit -- --nocapture ``` ### Impact This is a memory-safety hardening issue with demonstrated untrusted-input reachability. What is demonstrated: - current local agent peers could previously reach allocation growth directly from attacker-controlled frame lengths - historical remote SSH traffic could previously reach `CryptoVec` through transport and compression buffers in `russh < 0.58.0` - under constrained memory, the historical remote compression path can be turned into a process abort in the old Unix `CryptoVec` code - the fixed code now rejects oversized agent frames early and hardens the underlying allocation paths What is not demonstrated: - practical code execution - a demonstrated integrity or confidentiality break

Source published
21 May 2026 20:49:07 UTC
Source modified
10 Sep 2026 03:51:05 UTC
Observed here
24 Oct 2026 13:19:54 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

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

Publisher rating · HIGH

osv · RUSTSEC-2026-0153

Published

`CryptoVec` used unchecked capacity growth, unchecked length arithmetic, and unsafe allocation and locking paths. In affected `russh` releases, attacker-controlled input could reach these code paths through buffer resizing operations. Two affected reachability paths were identified: * **Current `russh` releases (`0.60.x` before the fix)** Local SSH agent peers could provide attacker-controlled frame lengths that were used to resize internal buffers before validation in: * `AgentClient::read_response` * `agent::server::Connection::run` * **Historical `russh` releases before `0.58.0`** `CryptoVec` was also used for non-secret transport and compression buffers, allowing remote SSH traffic to trigger `CryptoVec` growth through: * transport packet reads * zlib decompression output These remote paths were removed in `0.58.0` when `CryptoVec` stopped being used for those buffers. Under constrained memory conditions, historical `russh` versions prior to `0.58.0` can abort the process when remote compressed payload expansion causes allocation failure in `CryptoVec`. This was reproduced through the compression path and resulted in process termination in the Unix allocation/locking implementation after null pointer allocation failure. For current affected releases, oversized local SSH agent frame lengths could trigger untrusted-input-driven buffer growth prior to validation. No practical remote code execution, integrity or confidentiality impact has been demonstrated. Fixed by validating CryptoVec growth operations and rejecting oversized SSH agent frame lengths before buffer allocation.

Source published
15 May 2026 12:00:00 UTC
Source modified
12 Oct 2026 18:00:09 UTC
Observed here
24 Oct 2026 13:19:54 UTC
Source terms
OSV · upstream source license applies

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.

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

osv · RUSTSEC-2026-0154

Published

Both the SSH agent server and client accepted peer-controlled frame lengths without enforcing a maximum frame size. This could cause large memory allocations while parsing a maliciously crafted agent frame. A malicious peer could advertise an oversized frame length, causing the client or server to attempt a large memory allocation before validating the frame, potentially leading to memory exhaustion or process termination. This is fixed by enforcing a maximum agent frame size of 256 KiB and rejecting oversized frames before buffer allocation.

Source published
15 May 2026 12:00:00 UTC
Source modified
12 Oct 2026 18:00:13 UTC
Observed here
24 Oct 2026 13:19:54 UTC
Source terms
OSV · upstream source license applies

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.

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

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 10 Nov 09:28 UTC · 275 of 275 readable · 273 fully read · documentation 469/469 current