This entry demonstrates a state transition in portable OpenSSH ssh-agent that lets a forwarded agent connection reach the provider-add path after the agent moves from locked to unlocked. The replay uses an unchanged release build of OpenSSH 10.4p1, an actual ssh -A connection, public-key authentication, and a stock sshd listener.
The remote process keeps its forwarded SSH_AUTH_SOCK connection open while the agent is locked. The client sends session-bind@openssh.com, the locked agent rejects the extension request, and the SSH client still creates the forwarded agent channel. When the agent is unlocked through a separate local connection, the persistent forwarded socket has neither a recorded session identifier nor the failed-bind marker used by socket_is_remote(). A provider-add request on that socket is consequently handled as a local request.
| Field | Value |
|---|---|
| Product | OpenSSH portable |
| Release | 10.4p1 |
| Release date | July 6, 2026 |
| Platform | Ubuntu 24.04.4 LTS x86-64 under WSL2 |
| Kernel | Linux 6.6.87.2-microsoft-standard-WSL2 |
| Crypto library | OpenSSL 3.0.13 |
| Source archive | openssh-10.4p1.tar.gz |
| Archive SHA-256 | ef6026dd2aea8d56059638d5d3262902c892ceba9f88395835e0d06d3fb63238 |
ssh-agent SHA-256 |
c50a8cd9b1016dccfd21cb9b2f98e0de5285c27a0db87a8a7ae464bcd40d2e0d |
ssh SHA-256 |
d41006f31c89164da9f3c778f085d56cc0077ff49b1aca361863386ed8a068e9 |
sshd SHA-256 |
7420fe8020cb3e4a895f3c7eecd3327bcca78f6b4c28b3c6a267b6cfb1cfcdb9 |
| Provider used for replay | /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so |
The archive checksum matched the OpenSSH release checksum. Its detached signature verified with OpenSSH release key fingerprint 7168 B983 815A 5EEF 59A4 ADFD 2A3F 414E 7360 60BA. The binaries were produced with the release configure, make, and make install flow.
| Path | Purpose |
|---|---|
poc.py |
Sends agent lock and unlock requests and issues the provider request through the real forwarded socket. |
run.sh |
Starts the selected stock OpenSSH binaries, creates an isolated loopback SSH configuration, drives the sequence, and verifies the diagnostic markers. |
evidence/stock-openssh-10.4p1.txt |
Output and selected stock-process diagnostics from the repository replay. |
The state mismatch spans the OpenSSH client and agent:
clientloop.ccreates a new forwarded agent channel inclient_request_agent()even whenssh_agent_bind_hostkey()reports that the agent refused the session bind.ssh-agent.cextracts the request type inprocess_message()and applies the global locked check before its main dispatcher.- While locked, every extension request is discarded before
process_extension()can parse its name. process_ext_session_bind()therefore never setssession_bind_attemptedfor the forwarded socket.- After a separate local socket unlocks the agent,
socket_is_remote()sees bothsession_bind_attemptedand the session-identifier count as zero. process_add_smartcard_key()accepts the provider request because its remote-socket condition evaluates false.pkcs11_add_provider()startsssh-pkcs11-helper, which loads the selected module, resolves the PKCS#11 entry point, and initializes the provider.
The same socket classification is consulted by the external security-key provider path.
run.sh performs the following sequence with the binaries selected by OPENSSH_PREFIX:
- Generate temporary Ed25519 host and client keys.
- Start the selected
ssh-agenton an isolated Unix socket. - Start the selected
sshdon loopback with public-key authentication and agent forwarding enabled. - Lock the agent through its protocol.
- Connect with the selected
ssh -Aand runpoc.py probeas the remote process. - Hold the real forwarded agent socket open after the locked agent refuses session binding.
- Unlock the agent through a separate local socket.
- Send
SSH_AGENTC_ADD_SMARTCARD_KEYthrough the persistent forwarded socket. - Require the stock client log to show the refused bind followed by agent-channel creation.
- Require the stock agent log to show helper startup and provider initialization.
The temporary listener accepts connections only on 127.0.0.1, disables password authentication, and uses a generated key dedicated to the replay.
- Linux with Python 3,
sudo, and the OpenSSH privilege-separation account used bysshd. - A stock portable OpenSSH installation containing
ssh,sshd,ssh-agent, andssh-keygenunder one prefix. - A PKCS#11 module permitted by the agent's provider allowlist.
- A free loopback TCP port.
On Ubuntu 24.04 x86-64, the default provider path used by the script is supplied by p11-kit-modules.
Run from this folder and point OPENSSH_PREFIX at the stock installation:
OPENSSH_PREFIX=/opt/openssh-10.4p1 bash run.shThe listener defaults to port 22991. Select another port when needed:
OPENSSH_PREFIX=/opt/openssh-10.4p1 PORT=23022 bash run.shSelect a different permitted provider with PROVIDER:
OPENSSH_PREFIX=/opt/openssh-10.4p1 \
PROVIDER=/usr/lib/x86_64-linux-gnu/pkcs11/example.so \
bash run.shPreserve the generated client, agent, and server logs with KEEP=1:
OPENSSH_PREFIX=/opt/openssh-10.4p1 KEEP=1 bash run.shA successful replay prints:
lock_reply_type=6
unlock_reply_type=6
forwarded_socket_connected=true
provider_reply_type=5
target=OpenSSH_10.4p1, OpenSSL 3.0.13 30 Jan 2024
session_bind_refused_while_locked=true
forwarded_channel_opened=true
provider_helper_started=true
provider_initialized=true
reproduced=true
The stock client trace contains these ordering markers:
client_request_agent: ssh_agent_bind_hostkey: agent refused operation
channel 1: new agent-connection [authentication agent connection]
confirm agent-connect
The stock agent trace contains:
agent locked
process_message: socket 1 type 27
agent unlocked
process_message: socket 1 type 20
process_add_smartcard_key: add /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so
pkcs11_start_helper: starting .../ssh-pkcs11-helper -vvv
provider /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so: manufacturerID <PKCS#11 Kit>
The protocol reply is emitted after the helper has loaded and initialized the provider. The replay decision is based on the ordered client and agent diagnostics rather than the final reply byte.
The locked-dispatch ordering and the provider classification path are present in the portable tags V_9_3_P2, V_9_4_P1, V_9_5_P1, V_9_6_P1, V_10_0_P2, V_10_3_P1, and V_10_4_P1. The provider restriction begins in 9.3p2; the additional failed-session-bind tracking begins in 9.6p1 and remains behind the locked gate for this sequence.
Process session-bind@openssh.com while the agent is locked so the per-socket security state is recorded before any later unlock. Extension handling at the locked gate should admit only operations explicitly designed for locked state. Regression coverage should preserve the forwarded socket across lock and unlock, then verify that provider addition remains classified as remote.
Use this material only on systems you own or have explicit permission to test.
