cargo package report

Is pageant safe?

1 known vulnerability, worst severity MODERATE.

// reach

0 direct dependencies

none carry a known advisory

    0 packages depend on it

    an advisory here reaches each of them

      Create a free accountfor every dependency path, dependent and what to upgrade
      // ai model usage

      Tracked for PyPI packages. HuggingFace models declare Python dependencies, so cargo packages are not covered.


      cvss
      0.0
      medium

      severity out of 10

      epss
      0.00%
      low

      chance of exploitation in 30 days, 2nd percentile of all CVEs

      xyz score
      not scored

      CyberXYZ composite out of 10

      fig. 01 — GHSA-g4mp-vgx3-xrvm, the advisory selected below

      // 1 advisories

      GHSA-g4mp-vgx3-xrvm

      MODERATECVE-2026-102820
      // summary

      MemoryMap::read in the pageant crate (part of the russh workspace, used by russh's SSH-agent client on Windows via AgentClient::connectpageant) copies a peer-controlled number of bytes out of an 8192-byte shared-memory view with no bounds check — unlike the sibling MemoryMap::write, which correctly rejects oversize access with Error::Overflow. The byte count comes straight from a u32 length prefix that the responding "Pageant" process writes into the shared mapping. A malicious local process that answers as the Pageant agent can therefore cause:

      • an out-of-bounds read past the 8 KiB view (access violation → process

      crash; or disclosure of adjacent process memory if the following page is committed)

      • an allocation of up to ~4 GiB from a single u32 (vec![0; n]).

      This was reproduced end-to-end against the real, unmodified pageant crate (not a model) on x8664-pc-windows-gnu under Wine; see "Proof of concept".

      // impact
      • Availability / DoS (reliable). MemoryMap::read(size) walks off the end of

      the 8192-byte view and faults on the next, unmapped page — an EXCEPTIONACCESSVIOLATION that crashes the russh SSH client. Independently, a size near u32::MAX drives a ~4 GiB vec![0; n] before any copy.

      • Confidentiality (conditional). If memory immediately after the mapped view

      happens to be committed, read returns those adjacent bytes to russh as the "agent response", which russh then parses as agent identities/signatures. This arm depends on process memory layout, so it is opportunistic; the crash/alloc is the deterministic outcome.

      • Trust boundary. russh locates the agent with

      FindWindowW("Pageant", "Pageant") and passes the shared-mapping name inside the WMCOPYDATA COPYDATASTRUCT. Any local process can register a window of class + title "Pageant", receive that name, open the same mapping, and write a hostile size. So an unprivileged local process impersonating Pageant can attack every russh-based SSH client that uses the Pageant agent.

      // affected component
      • pageant/src/wmmessage.rs
      • MemoryMap::read (:160-171) — no bound (contrast MemoryMap::write

      :139-158, which returns Error::Overflow when pos + len > length).

      • querypageantdirect (:199-237) — reads a 4-byte u32 size from the

      shared mapping (:233) and calls map.read(size) (:234) with no check against AGENTMAXMSGLEN (8192).

      • Reached from russh via AgentClient::connectpageant → PageantStream →

      querypageantdirect.

      Platform: Windows only (cfg(windows)), local attacker. Verified against the pageant crate v0.2.2 as shipped in russh v0.63.1 (d3ae702, the latest release). read has never had a bound in any revision (git log -p -- pageant/src/wmmessage.rs).

      // details
      fn write(&mut self, data: &[u8]) -> Result {
          if self.pos + data.len() > self.length {      // :140  BOUND PRESENT
              return Err(Error::Overflow);
          }
          ... copy_nonoverlapping(&data[0], view+pos, data.len()) ...
      }
      
      fn read(&mut self, n: usize) -> Vec {         // :160  NO BOUND
          let out = vec![0; n];                         // n up to 0xFFFF_FFFF (CWE-789)
          unsafe {
              std::ptr::copy_nonoverlapping(
                  self.view.Value.add(self.pos) as *const u8,   // view is length==8192
                  out.as_ptr() as *mut u8,
                  n,                                    // reads n bytes, may run past the view (CWE-125)
              );
          }
          self.pos += n;
          out
      }

      querypageantdirect creates the mapping at AGENTMAXMSGLEN = 8192, writes the request, sends the WMCOPYDATA, then reads the response the peer wrote:

      map.seek(0);
      let mut buf = map.read(4);
      let size = u32::from_be_bytes([buf[0],buf[1],buf[2],buf[3]]) as usize; // :233 peer-controlled
      buf.extend(map.read(size));                                            // :234 unbounded

      Nothing checks 4 + size <= 8192, so map.read(size) runs past the 8 KiB view.

      // proof of concept

      Because the bug is Windows-only (WMCOPYDATA + MapViewOfFile), the PoC is a Windows cross-build (x8664-pc-windows-gnu) driven under Wine, entirely inside a Linux container. It exercises the real, unmodified crate: the PoC takes a path dependency on pageant and calls pageant::wmmessage::querypageantdirect, exactly what AgentClient::connectpageant uses. A second thread impersonates Pageant (registers the window class + title "Pageant") and, on WMCOPYDATA, opens the shared mapping russh created and writes an attacker-chosen 4-byte big-endian length.

      poc/run.sh builds and runs it. Full log in results/e2e-wine-run.log:

      ======== LEG 1 — ATTACK: fake agent reports length 0x00080000 (512 KiB) >> 8192-byte view ========
      [attacker] impersonating Pageant window is up (class+title "Pageant")
      [victim ] calling pageant::wmmessage::query_pageant_direct() over the 8192-byte view ...
      [attacker] WM_COPYDATA received; shared mapping = "PageantRequestpoc"
      [attacker] wrote hostile response length = 524288 (0x00080000) into the 8192-byte view
      wine: Unhandled page fault on read access to 0000000002092000 at address 00000002282CFDC4 ...
      WINE-EXIT=5
      
      ======== LEG 2 — CONTROL: fake agent reports length 0x00000010 (16 B), in-bounds ========
      [victim ] returned 20 bytes — request+response fit inside the 8192-byte view (in-bounds control); no fault
      WINE-EXIT=0
      • LEG 1 (attack): an oversized size makes MemoryMap::read read past the

      8192-byte view; Wine reports an unhandled page fault at a page-aligned address (0x…2092000) — the out-of-bounds read as an access violation (crash). Exit code 5 = STATUSACCESSVIOLATION.

      • LEG 2 (control): an in-bounds size returns cleanly. Same code path; the

      only difference is whether the peer's length exceeds the view — isolating the missing bound.

      Fix validation. With patch/pageant-read-bound.patch applied and the PoC rebuilt against the patched crate, the identical attack input is rejected (results/patched-wine-run.log):

      [attacker] wrote hostile response length = 524288 (0x00080000) into the 8192-byte view
      [victim ] query_pageant_direct error: Overflow
      WINE-EXIT=0

      No fault; the read is refused at the bound, exactly as write already refuses oversize writes. (A pure-logic Linux model of the same control flow is also included as poc/pageantreadoobdemo.rs / results/logic-demo-run.log.)

      // remediation

      Mirror write's guard in read and validate the response length before allocating/copying. patch/pageant-read-bound.patch:

      • MemoryMap::read(n) returns Result, Error> and returns

      Error::Overflow when self.pos + n > self.length;

      • querypageantdirect propagates that Result and additionally rejects

      size > AGENTMAXMSGLEN - 4 before map.read(size).

      // cvss v3.1 vector

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

      Attack vector
      Local
      Attack complexity
      Low
      Privileges required
      None
      User interaction
      None
      Scope
      Unchanged
      Confidentiality
      None
      Integrity
      None
      Availability
      High

      Checked 2026-10-01 at 23:58 UTC. The most recent advisory here was published 2026-09-30. Updated continuously from NVD, GHSA, OSV and CNA feeds.

      Think a verdict here is wrong? Tell us — we respond within 2 business days.