Skip to content

fix(pageant): include NUL terminator in WM_COPYDATA payload - #747

Closed
litcc wants to merge 1 commit into
Eugeny:mainfrom
litcc:fix/pageant-copydata-nul
Closed

fix(pageant): include NUL terminator in WM_COPYDATA payload#747
litcc wants to merge 1 commit into
Eugeny:mainfrom
litcc:fix/pageant-copydata-nul

Conversation

@litcc

@litcc litcc commented Aug 13, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes the legacy WM_COPYDATA Pageant transport by including the
terminating NUL byte in COPYDATASTRUCT.cbData.

Background

This issue was discovered while developing and testing the SSH-agent
forwarding feature in the NyaTerm project.

On Windows, NyaTerm exhibited the following symptoms when authenticating
through Pageant:

SSH Agent identity request failed: early eof

Pageant was running with a valid software key loaded. The failure
occurred on a local, non-domain Windows account where
GetUserNameExA(NameUserPrincipal) could not resolve the user principal
name. The russh Pageant client then fell back from the named-pipe
transport to the legacy WM_COPYDATA transport.

The same failure was reproduced by NyaTerm's Windows Pageant integration
test.

Root cause

The Pageant mapping name is created as a CString, so the allocated
buffer is NUL-terminated. However, the previous implementation used
CString::as_bytes() when populating COPYDATASTRUCT:

cbData: char_buffer.as_bytes().len() as u32,
lpData: char_buffer.as_bytes().as_ptr() as *mut _,

as_bytes() excludes the terminating NUL from the reported length.
Therefore, although lpData points to a NUL-terminated buffer,
cbData does not include that byte.

The PuTTY Pageant implementation explicitly validates that the data
described by COPYDATASTRUCT is an ASCIZ string and rejects the request
when the final byte is not NUL-terminated:

PuTTY Pageant implementation

The rejected request causes the client-side stream to observe an early
EOF.

Fix

Use CString::as_bytes_with_nul() for both the payload length and
pointer:

cbData: char_buffer.as_bytes_with_nul().len() as u32,
lpData: char_buffer.as_bytes_with_nul().as_ptr() as *mut _,

This ensures that cbData includes the terminating NUL required by the
Pageant WM_COPYDATA protocol.

The transport selection, memory mapping, and request flow remain
unchanged.

Validation

The fix was validated on Windows with Pageant running and a software
Ed25519 key loaded.

The NyaTerm Pageant integration test was run with:

$env:NYATERM_TEST_WINDOWS_PAGEANT = "1"

cargo test --locked `
    --manifest-path .\src-tauri\Cargo.toml `
    --lib `
    windows_pageant_lists_and_signs_identity `
    -- --ignored --nocapture

The test passed after changing only the WM_COPYDATA payload handling:

test core::ssh::agent::windows_integration_tests::windows_pageant_lists_and_signs_identity ... ok

The named-pipe implementation was left unchanged. This confirms that
the failure was isolated to the WM_COPYDATA path.

Additional checks:

cargo fmt --all -- --check
git diff --check

Scope

This is a minimal WM_COPYDATA protocol-correctness fix.

It is independent of
#726, which addresses
username fallback for the Windows named-pipe transport.

No named-pipe behavior is changed by this PR.

Pageant expects the mapping name sent through WM_COPYDATA to be an
ASCIZ string and validates the final byte of cbData. The previous code
used CString::as_bytes(), so cbData omitted the terminating NUL even
though the pointer referenced a NUL-terminated buffer. Pageant
therefore rejected the request and the client observed an early EOF.

Use as_bytes_with_nul() for both the payload length and pointer.
@litcc

litcc commented Aug 15, 2026

Copy link
Copy Markdown
Author

I just realized this may overlap with PR #624.
Its latest commits already include the same WM_COPYDATA NUL-termination fix.
If PR #624 is merged as-is, this PR may become redundant and could be closed as a duplicate.

@Eugeny

Eugeny commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Thank you! I've decided to go with #624

@Eugeny Eugeny closed this Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants