{"id":"EUVD-2026-111390","revision":null,"records":[{"revision":"495fcd8d040041d68b4a7827e10abd03","observedAt":"2026-10-27T21:45:55.39+00:00","statement":{"source":"cve-program","id":"CVE-2026-116775","aliases":[],"title":"pageant: unbounded frame buffering on peer-controlled 4-byte length prefix (memory-exhaustion DoS)","description":"pageant provides a PageantStream type that implements AsyncRead and AsyncWrite traits and can be used to communicate with a running Pageant instance. From 0.2.0 until 0.2.5, PageantStream::new in pageant/src/wmmessage.rs trusts a peer-controlled four-byte length prefix and buffers until len + 4 bytes are available without checking the fixed _AGENT_MAX_MSGLEN transport capacity. An application that bridges an SSH forwarded-agent channel to the raw stream can allow a malicious server to force large allocations or retain a partially filled frame, causing client memory exhaustion. This issue is fixed in version 0.2.5.","state":0,"publishedAt":"2026-10-27T20:27:30.852+00:00","modifiedAt":"2026-10-27T20:27:30.852+00:00","affected":[{"ecosystem":"Vendor product","name":"russh","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"Eugeny\",\n                    \"product\": \"russh\",\n                    \"versions\": [\n                        {\n                            \"version\": \">= 0.2.0, < 0.2.5\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","score":2.3,"severity":"LOW"}],"references":["https://github.com/Eugeny/russh/security/advisories/GHSA-662x-8wvj-2276","https://github.com/Eugeny/russh/commit/69e4b55513eaa8ac9d2ad4236f13171fc164e10a","https://github.com/Eugeny/russh/releases/tag/v0.65.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-116775","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-116775","identifiers":["CVE-2026-116775"]},"retained":false,"historicalAffected":[]},{"revision":"ef3f477ddfc343b19ab8825eab8d9851","observedAt":"2026-10-30T23:48:10.643+00:00","statement":{"source":"euvd","id":"EUVD-2026-111390","aliases":["CVE-2026-116775"],"title":"pageant provides a PageantStream type that implements AsyncRead and AsyncWrite traits and can be used to communicate with a running Pageant instance. From 0.2.0 until 0.2.5, PageantStream::new in pageant/src/wmmessage.rs trusts a peer-contr…","description":"pageant provides a PageantStream type that implements AsyncRead and AsyncWrite traits and can be used to communicate with a running Pageant instance. From 0.2.0 until 0.2.5, PageantStream::new in pageant/src/wmmessage.rs trusts a peer-controlled four-byte length prefix and buffers until len + 4 bytes are available without checking the fixed _AGENT_MAX_MSGLEN transport capacity. An application that bridges an SSH forwarded-agent channel to the raw stream can allow a malicious server to force large allocations or retain a partially filled frame, causing client memory exhaustion. This issue is fixed in version 0.2.5.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","score":2.3,"severity":null}],"references":["https://github.com/Eugeny/russh/security/advisories/GHSA-662x-8wvj-2276","https://github.com/Eugeny/russh/commit/69e4b55513eaa8ac9d2ad4236f13171fc164e10a","https://github.com/Eugeny/russh/releases/tag/v0.65.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-111390","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 27, 2026, 8:27:30 PM","dateUpdated":"Oct 27, 2026, 8:27:30 PM"},"relatedAdvisories":[],"note":"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."},"key":"euvd:EUVD-2026-111390","identifiers":["EUVD-2026-111390","CVE-2026-116775"]},"retained":false,"historicalAffected":[]},{"revision":"0f03a38e564545e2b4a310bfe2f7852e","observedAt":"2026-10-30T23:47:40.018+00:00","statement":{"source":"nvd","id":"CVE-2026-116775","aliases":[],"title":"pageant provides a PageantStream type that implements AsyncRead and AsyncWrite traits and can be used to communicate with a running Pageant instance. From 0.2.0 until 0.2.5, PageantStream::new in pageant/src/wmmessage.rs trusts a peer-contr…","description":"pageant provides a PageantStream type that implements AsyncRead and AsyncWrite traits and can be used to communicate with a running Pageant instance. From 0.2.0 until 0.2.5, PageantStream::new in pageant/src/wmmessage.rs trusts a peer-controlled four-byte length prefix and buffers until len + 4 bytes are available without checking the fixed _AGENT_MAX_MSGLEN transport capacity. An application that bridges an SSH forwarded-agent channel to the raw stream can allow a malicious server to force large allocations or retain a partially filled frame, causing client memory exhaustion. This issue is fixed in version 0.2.5.","state":0,"publishedAt":"2026-10-27T21:18:06.103+00:00","modifiedAt":"2026-10-27T21:18:06.33+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · security-advisories@github.com · Secondary","vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","score":2.3,"severity":"LOW"}],"references":["https://github.com/Eugeny/russh/commit/69e4b55513eaa8ac9d2ad4236f13171fc164e10a","https://github.com/Eugeny/russh/releases/tag/v0.65.0","https://github.com/Eugeny/russh/security/advisories/GHSA-662x-8wvj-2276"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-116775","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-770"],"productCriteria":[],"sourceDates":{"published":"2026-10-27T21:18:06.103","lastModified":"2026-10-27T21:18:06.330"},"relatedAdvisories":[],"note":"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."},"key":"nvd:CVE-2026-116775","identifiers":["CVE-2026-116775"]},"retained":false,"historicalAffected":[]},{"revision":"da1672da964040289123ac5c6a5ef99f","observedAt":"2026-10-30T23:47:35.544+00:00","statement":{"source":"osv","id":"GHSA-662x-8wvj-2276","aliases":["CVE-2026-116775"],"title":"pageant: unbounded frame buffering on peer-controlled 4-byte length prefix (memory-exhaustion DoS)","description":"## Summary\n\n`pageant::PageantStream` reads a 4-byte big-endian length prefix from its input stream and buffers until `len + 4` bytes are available, without ever validating `len`. The underlying WM_COPYDATA transport physically cannot carry frames larger than 8 KiB (`_AGENT_MAX_MSGLEN`, the fixed-size shared memory mapping), so an oversized frame is destined to fail — but only after the re-framing task has already buffered the full claimed size. A peer controlling the stream bytes can claim a frame of up to ~4 GiB and either stream that much data (forcing a corresponding allocation) or simply stop mid-frame and keep the partial buffer resident indefinitely.\n\n## Vulnerable code\n\n`pageant/src/wmmessage.rs`, `PageantStream::new()` (the spawned re-framing task), current main:\n\n```rust\nlet mut buf = BytesMut::new();\nwhile let Ok(n) = two.read_buf(&mut buf).await {\n    if n == 0 { break; }\n    if buf.len() < 4 { continue; }\n    let len = BigEndian::read_u32(&buf) as usize;\n    if buf.len() < len + 4 { continue; }   // <-- `len` is trusted; no upper bound\n    let msg = buf.split_to(len + 4).freeze();\n    // ...\n}\n```\n\n`len` is consumed verbatim from the stream, so `buf` grows towards `len + 4`. Even a \"complete\" oversized frame cannot succeed: `query_pageant_direct()` writes `msg` into the `_AGENT_MAX_MSGLEN`-sized (8192 bytes) shared mapping and fails with `Error::Overflow` for anything larger — the unbounded buffering is therefore pure attacker-controlled memory growth.\n\nHistory: introduced in commit `6eac7a157dd2cc81b8fafdb2977f1c388fe9784b` (\"fix Tabby#10301 Still cannot login with pageant\", PR #565, 2025-09-25) together with the length-prefixed framing itself; present in every 0.2.x release (0.1.0 predates the length-prefixed loop and is not affected by this issue).\n\n## Attack scenario (SSH agent forwarding)\n\nThe crate is documented as a raw transport (\"This crate only implements the transport, not the actual SSH agent protocol\"). The standard way to service a remote server's forwarded agent channel on Windows is to bridge the SSH channel to the local agent, e.g.:\n\n```rust\n// inside Handler::server_channel_open_agent_forward (russh client, sketch)\nlet mut channel = channel.into_stream();          // bytes sent by the SSH server\nlet mut agent = pageant::PageantStream::new().await?;\ntokio::io::copy_bidirectional(&mut channel, &mut agent).await?;\n```\n\nIn this (intended) bridging use, the bytes written into `PageantStream` are the SSH server's channel payload, so the 4-byte length prefix is controlled by a malicious or compromised SSH server. The server only needs to send the four bytes `FF FF F0 00`, then stream data (or stall). russh's typed `AgentClient` bounds the response frames it reads with `MAX_AGENT_FRAME_LEN` (256 KiB) and builds its own request frames, but the raw stream path — the documented transport usage — has no equivalent bound.\n\nImpact: remote memory-exhaustion DoS of the client process. A full-size claim lets the peer force buffering of up to ~4 GiB of memory, which on memory-constrained hosts ends in an allocation-failure abort (uncatchable in Rust). Multiple forwarded channels multiply the effect. No memory-safety violation was observed.\n\n## Proof of concept\n\n(Windows; no Pageant instance required — buffering happens before any Pageant interaction)\n\n```rust\nuse tokio::io::AsyncWriteExt;\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let mut stream = pageant::PageantStream::new().await?;\n    // Claim a ~4 GiB frame, stream 256 MiB, then stall: the task buffers all of it.\n    stream.write_all(&0xFFFF_F000u32.to_be_bytes()).await?;\n    let chunk = vec![0u8; 256 * 1024];\n    for _ in 0..1024 { stream.write_all(&chunk).await?; } // 256 MiB, frame never completed\n    tokio::time::sleep(std::time::Duration::from_secs(30)).await;\n    Ok(())\n}\n```\n\nObserved on Windows 11, pageant 0.2.4 (source v0.64.1, Rust 1.99): process working set grows from ~7.4 MB to ~264.7 MB after 256 MiB is streamed, and stays there while the peer stalls; no error is ever raised. Memory grows linearly with bytes sent, up to the claimed length. (Allocation behavior additionally verified with a counting global allocator.)\n\n## Suggested fix\n\nReject frames that cannot fit the transport before buffering:\n\n```diff\n                 let len = BigEndian::read_u32(&buf) as usize;\n+                // The WM_COPYDATA transport is backed by a fixed-size shared\n+                // memory mapping: larger requests can never fit. Reject them\n+                // before buffering. Without this cap a malicious peer could\n+                // force up to ~4 GiB of buffering by claiming a huge frame\n+                // and never completing it.\n+                if len.saturating_add(4) > _AGENT_MAX_MSGLEN {\n+                    debug!(\"dropping oversized Pageant request: {len} bytes\");\n+                    break;\n+                }\n                 if buf.len() < len + 4 {\n                     continue;\n                 }\n```\n\n`saturating_add` also removes a theoretical 32-bit `len + 4` overflow. `break` shuts down the re-framing task (the writer observes the stream close); returning an error through the stream would work equally well. Happy to submit a PR with this change, or with any alternative you prefer.\n\n## Reporter\n\nTshellauth","state":0,"publishedAt":"2026-10-30T22:59:00+00:00","modifiedAt":"2026-10-30T23:15:08.2283038+00:00","affected":[{"ecosystem":"crates.io","name":"pageant","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0.2.0"},{"kind":"fixed","version":"0.2.5"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V4","vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"LOW"}],"references":["https://github.com/Eugeny/russh/security/advisories/GHSA-662x-8wvj-2276","https://nvd.nist.gov/vuln/detail/CVE-2026-116775","https://github.com/Eugeny/russh/commit/69e4b55513eaa8ac9d2ad4236f13171fc164e10a","https://github.com/Eugeny/russh","https://github.com/Eugeny/russh/releases/tag/v0.65.0"],"sourceUrl":"https://github.com/advisories/GHSA-662x-8wvj-2276","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-662x-8wvj-2276","identifiers":["GHSA-662x-8wvj-2276","CVE-2026-116775"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-31T19:04:56.1330465+00:00","complete":true,"records":1766,"lastAttemptAt":"2026-10-31T19:04:56.1330465+00:00","lastSuccessAt":"2026-10-31T19:04:56.2887005+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:04:56.2887022+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-9266d7b009bf9c4a0ccdcb5cbdea7f905cb3c4d92fd3b41581ece16b2cfdc4e7-c71479685b1713e2042fb88ec15880046552dd37e95ce2a15e200aae212a9461","complete":true,"records":417592,"lastAttemptAt":"2026-10-31T19:04:55.457276+00:00","lastSuccessAt":"2026-10-31T19:04:55.3095682+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:04:55.457276+00:00","snapshotPublishedAt":"2026-10-31T18:19:52+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-e7b141564b3340aebe07d7339b385d4a","complete":true,"records":13567,"lastAttemptAt":"2026-10-31T19:05:27.0056627+00:00","lastSuccessAt":"2026-10-31T19:05:27.002+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:05:27.002+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13637,"expectedIdentifiers":13637,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-31T19:04:56.1330465+00:00","complete":true,"records":1094,"lastAttemptAt":"2026-10-31T19:04:56.1330465+00:00","lastSuccessAt":"2026-10-31T19:04:56.2895472+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:04:56.289548+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-a558c2e000674838b5341fe81ce273de","complete":false,"records":13580,"lastAttemptAt":"2026-10-31T19:05:21.5191534+00:00","lastSuccessAt":"2026-10-31T19:05:21.516+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:05:21.516+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13636,"expectedIdentifiers":13637,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-21dcd6662970dfea02e4ea60ee637115e8d92664947484d2b9276b3f6568a84f","complete":true,"records":9815,"lastAttemptAt":"2026-10-31T19:04:56.081418+00:00","lastSuccessAt":"2026-10-30T23:47:34.1817587+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:04:56.081418+00:00","snapshotPublishedAt":"2026-10-30T23:33:17+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-2de9ccc5290f060c7aa63f89c25dd76f655a64ba1aba7c05c97fa3bee4f09899","complete":true,"records":1967,"lastAttemptAt":"2026-10-31T19:04:55.5925297+00:00","lastSuccessAt":"2026-10-28T20:55:34.2982937+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:04:55.5925297+00:00","snapshotPublishedAt":"2026-10-28T20:03:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-f95f76cd406ae5bae3de632ad37fd2d5160b44f5e6b51748ac6f8f0c0c88e72b","complete":true,"records":3028,"lastAttemptAt":"2026-10-31T19:04:56.0947894+00:00","lastSuccessAt":"2026-10-30T23:47:38.4914494+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:04:56.0947894+00:00","snapshotPublishedAt":"2026-10-30T23:18:12+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-712291bb5a0ee4f1ca7325e317f98ff05e9ab8d14f863abf00a323d9e6d8331e","complete":true,"records":230994,"lastAttemptAt":"2026-10-31T19:04:55.7662022+00:00","lastSuccessAt":"2026-10-31T17:06:47.8999408+00:00","error":null,"lastVerifiedAt":"2026-10-31T19:04:55.7662022+00:00","snapshotPublishedAt":"2026-10-31T16:33:38+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}