GHSA-g9f8-wqj9-fjw5 Russh: Unchecked CryptoVec allocation and growth handling is reachable
TitleUnchecked `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` releasesSummary`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.DetailsThe underlying unsafe paths were in `CryptoVec`:`cryptovec/src/cryptovec.rs`unchecked capacity growthunchecked length arithmetic in growth callersraw 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 pathThere are two relevant reachability stories: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 payloadThis 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 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 readszlib decompression outputAlso 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 `CryptoVec` itselfchecked capacity growthchecked length arithmeticimmediate allocation-failure handlingzero-length `mlock` / `munlock` no-opsexplicit null-pointer validation before entering the Unix `unsafe` locking callshardens the real untrusted-input pathcaps agent frame lengths at `256 * 1024` on both client and server before resizing buffersThis cap matches OpenSSH’s agent framing guardrail.PoCThe 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:ImpactThis 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 lengthshistorical 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` codethe fixed code now rejects oversized agent frames early and hardens the underlying allocation pathsWhat is not demonstrated:practical code executiona demonstrated integrity or confidentiality break
CVSS v3.1 score: 7.5