Описание
Russh: Unchecked CryptoVec allocation and growth handling is reachable
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.rsmlock/munlockpreviously accepted zero-length calls and performed null-pointer validation inside theunsafeOS-call path
There are two relevant reachability stories:
- current local reachability in
russh
russh/src/keys/agent/client.rsAgentClient::read_response()read a peer-suppliedu32length and then resizedself.bufto that value before reading the payload
russh/src/keys/agent/server.rsConnection::run()read a peer-suppliedu32length and then resizedself.bufto 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.
- historical remote reachability in older
russh
- before commit
712e32b(first released inv0.58.0), non-secret transport and compression buffers inrusshstill usedCryptoVec - I verified this in a detached pre-
712e32bworktree by adding and running:cipher::tests::remote_packet_length_grows_transport_cryptovec_buffercompression::tests::remote_compressed_payload_expands_cryptovec_output
- those tests show that remote SSH traffic could grow
CryptoVecthrough:- 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:
-
hardens
CryptoVecitself- checked capacity growth
- checked length arithmetic
- immediate allocation-failure handling
- zero-length
mlock/munlockno-ops - explicit null-pointer validation before entering the Unix
unsafelocking calls
-
hardens the real untrusted-input path
- caps agent frame lengths at
256 * 1024on both client and server before resizing buffers
- caps agent frame lengths at
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:
Server-side agent request path:
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:
Compression growth path:
Constrained-memory crash reproduction for the historical remote compression path:
On that historical worktree, the constrained-memory child aborts in the old Unix CryptoVec path with:
To run the reproduced checks:
Historical pre-0.58.0 checks were run from the detached 91d431d worktree with:
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
CryptoVecthrough transport and compression buffers inrussh < 0.58.0 - under constrained memory, the historical remote compression path can be turned into a process abort in the old Unix
CryptoVeccode - 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
Пакеты
russh-cryptovec
<= 0.60.2
0.60.3
russh
<= 0.60.2
0.60.3
Связанные уязвимости
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.
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.
Russh is a Rust SSH client & server library. Prior to version 0.60.3, ...