Skip to content

Fix client state hostkey wait timeout handling - #6

Merged
mzagozen merged 1 commit into
mainfrom
fix_CLIENT_STATE_HOSTKEY_WAIT_timeout_handling
Oct 16, 2026
Merged

mzagozen merged 1 commit into
mainfrom
fix_CLIENT_STATE_HOSTKEY_WAIT_timeout_handling

Conversation

@precla

@precla precla commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

i've found one possible issue and i have a test for it to reproduce the issue. both with the help of Claude, i did double check the code and the flow to verify it

connect_timeout doesn't cover host-key verification timeout within on_hostkey()

for example if on_hostkey() takes too long for whatever reason or if accept_hostkey()/reject_hostkey() are not called (even tho the last one doesn't make sense to not call one of the two..) then the timeout of c->connect_timer is never reached since it is stopped too soon in the moment the transport handshake completes (CLIENT_STATE_CONNECTING -> CLIENT_STATE_HOSTKEY), before host-key verification begins. once the client reaches CLIENT_STATE_HOSTKEY_WAIT, no timer is armed (the auth timer only starts later)

the ssh client's connect_timeout should handle the entire "getting connected" phase. currently is basically handles only CLIENT_STATE_CONNECTING, but not CLIENT_STATE_HOSTKEY_WAIT. which happens in between when the client receives the server's host key and is waiting for on_hostkey() handler to call accept_hostkey()/reject_hostkey() (or for whatever reason do some other 'magic')

the states CLIENT_STATE_HOSTKEY_WAIT and CLIENT_STATE_HOSTKEY are checked in connect_timeout_cb() so code should time-out in that case but it never does since those states never reach the connect_timeout_cb() if on_hostkey() takes too long

static void connect_timeout_cb(uv_timer_t *timer) {
    ssh_client_ctx *c = (ssh_client_ctx *)timer->data;
    if (c == NULL)
        return;
    if (c->state == CLIENT_STATE_CONNECTING || c->state == CLIENT_STATE_HOSTKEY || c->state == CLIENT_STATE_HOSTKEY_WAIT) {
        client_fail(c, "SSH connect timeout");
    }
}

if the on_hostkey() handler never responds then the client hangs forever. on_connect() is never called

note: the timeout only survives into CLIENT_STATE_HOSTKEY_WAIT on a synchronous connect, which never occurs over a non-blocking socket

proposed fix: stop the connect timer when authentication begins in client_start_auth_timer(), not when the transport connects

test _test_hostkey_stall added

@precla
precla force-pushed the fix_CLIENT_STATE_HOSTKEY_WAIT_timeout_handling branch 2 times, most recently from bec16c0 to 2eaebdc Compare August 27, 2026 11:38
@precla
precla force-pushed the fix_CLIENT_STATE_HOSTKEY_WAIT_timeout_handling branch from 2eaebdc to 8f765ef Compare September 4, 2026 05:14
@precla
precla force-pushed the fix_CLIENT_STATE_HOSTKEY_WAIT_timeout_handling branch from 8f765ef to e91888b Compare October 5, 2026 10:46
connect_timeout_cb acts on CONNECTING, HOSTKEY and HOSTKEY_WAIT, but
connect_timer was stopped as soon as ssh_connect() returned SSH_OK, and
auth_timer only starts once the host key is accepted. A client whose
on_hostkey never called accept_hostkey() or reject_hostkey() stayed in
HOSTKEY_WAIT with no timer armed until the server gave up.

Stop connect_timer where AUTH begins, in client_on_hostkey_accepted(),
which both the known_hosts path and accept_hostkey() now use.
client_on_ready() no longer stops connect_timer, since it is always
stopped by then. A timeout in HOSTKEY_WAIT fails with "SSH host key
verification timeout".

Signed-off-by: Antonio Prcela <antonio.prcela@sartura.hr>
Co-authored-by: Marko Zagožen <marko.zagozen@gmail.com>
@mzagozen
mzagozen force-pushed the fix_CLIENT_STATE_HOSTKEY_WAIT_timeout_handling branch from e91888b to 244920e Compare October 16, 2026 08:02
@mzagozen
mzagozen merged commit 5cdc9aa into main Oct 16, 2026
1 check passed
@mzagozen
mzagozen deleted the fix_CLIENT_STATE_HOSTKEY_WAIT_timeout_handling branch October 16, 2026 13:11
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