Loading...
Skip to main content

CVE-2026-102820 — Vulnerability in pageant — CVE Details

MEDIUM

pageant: Out-of-bounds read / oversized allocation in `pageant` MemoryMap::read via a malicious Pageant agent (Windows)

Published September 30, 2026Updated October 1, 2026Source: osv

Summary

## Summary `MemoryMap::read` in the `pageant` crate (part of the russh workspace, used by russh's SSH-agent client on Windows via `AgentClient::connect_pageant`) 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 `x86_64-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 `EXCEPTION_ACCESS_VIOLATION` 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 `WM_COPYDATA` `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`). - `query_pageant_direct` (`:199-237`) — reads a 4-byte `u32` size from the shared mapping (`:233`) and calls `map.read(size)` (`:234`) with no check against `_AGENT_MAX_MSGLEN` (8192). - Reached from russh via `AgentClient::connect_pageant` → `PageantStream` → `query_pageant_direct`. 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 ```rust fn write(&mut self, data: &[u8]) -> Result<(), Error> { 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<u8> { // :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 } ``` `query_pageant_direct` creates the mapping at `_AGENT_MAX_MSGLEN = 8192`, writes the request, sends the `WM_COPYDATA`, then reads the response the peer wrote: ```rust 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 (`WM_COPYDATA` + `MapViewOfFile`), the PoC is a Windows cross-build (`x86_64-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::query_pageant_direct`, exactly what `AgentClient::connect_pageant` uses. A second thread impersonates Pageant (registers the window class + title `"Pageant"`) and, on `WM_COPYDATA`, 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 = `STATUS_ACCESS_VIOLATION`. - **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/pageant_read_oob_demo.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<Vec<u8>, Error>` and returns `Error::Overflow` when `self.pos + n > self.length`; - `query_pageant_direct` propagates that `Result` and additionally rejects `size > _AGENT_MAX_MSGLEN - 4` before `map.read(size)`.

Advisory Timeline

  • First published September 30, 2026 (11 days ago).
  • Last revised October 1, 2026 — 0 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

Cargo
Update pageant to 0.2.3 or later
cargo update -p pageant --precise 0.2.3

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (1)

Affects 1 ecosystem: crates.io (1).

PackageEcosystemAffectedFixed In
pageant
crates.io
All versions0.2.3

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 6.2 score means for each attack dimension.

Attack Vector
Local
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

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

Frequently Asked Questions

What is CVE-2026-102820?
pageant: Out-of-bounds read / oversized allocation in `pageant` MemoryMap::read via a malicious Pageant agent (Windows) This vulnerability has been assigned a severity rating of MEDIUM (CVSS score: 6.2/10).
How do I check if my project is affected by CVE-2026-102820?
CVE-2026-102820 affects pageant. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-102820 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
6.2

Exploitation requires specific conditions or has limited impact. Remediate within weeks.

Also Known As

GHSA-g4mp-vgx3-xrvm

Related CVEs

  • CVE-2026-102821
    MEDIUM

    Russh: Unbounded memory exhaustion via CHANNEL_OPEN flood during a client-stalled rekey

  • CVE-2026-102342
    MEDIUM

    Ammonia: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies