From 6394848f82c7defae8c11e29a2a76dbabc9f607f Mon Sep 17 00:00:00 2001 From: John Safranek Date: Thu, 27 Aug 2026 14:27:21 -0700 Subject: [PATCH 1/2] echoserver: close the agent socket on reset and handle a rekey The worker drops back to APP_STATE_LISTEN when an agent connection ends, but never closes the socket. The next accept() overwrites agentFd, so every agent connection after the first leaks the previous descriptor. The forward path has the same gap on its connection-reset arm, where the socket is closed but fwdFd keeps the closed number. A rekey was treated as a read failure and ended the session. It cannot just be skipped either: wolfSSH_worker() reports WS_REKEYING in place of WS_CHAN_RXD while keying, and nothing raises the data report again, so ignoring it strands whatever arrived in that call and the peer waits on an answer that never comes. This is the hazard the library already calls out for WS_EXTDATA, which is exempted from the same override. - close agentFd and clear it on both the read-zero and the ECONNRESET/ECONNABORTED arms - clear fwdFd on the forward reset arm, matching the read-zero arm - clear agentCtx.appFd and fwdCtx.appFd wherever the worker closes the socket, so the stored copy cannot outlive the descriptor - drain the channel on WS_REKEYING as well as WS_CHAN_RXD, and take an empty read as "nothing buffered" rather than a failure on that path. wolfSSH_ChannelIdRead() has no isKeying gate and the window credit it owes is parked until the rekey completes --- examples/echoserver/echoserver.c | 39 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 2dd03dc8c..300ce5ff0 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -999,14 +999,25 @@ static int ssh_worker(thread_ctx_t* threadCtx) cnt_r = wolfSSH_worker(ssh, &lastChannel); if (cnt_r < 0) { rc = wolfSSH_get_error(ssh); - if (rc == WS_CHAN_RXD) { + /* wolfSSH_worker() reports WS_REKEYING in place of + * WS_CHAN_RXD while a rekey is in flight, and the data + * report is never raised again, so drain on both or the + * buffered bytes sit there and the peer waits forever. + * wolfSSH_ChannelIdRead() has no isKeying gate; the window + * credit it owes is parked until the rekey finishes. */ + if (rc == WS_CHAN_RXD || rc == WS_REKEYING) { if (lastChannel == threadCtx->shellCtx.channelId) { cnt_r = wolfSSH_ChannelIdRead(ssh, threadCtx->shellCtx.channelId, threadCtx->channelBuffer, sizeof threadCtx->channelBuffer); - if (cnt_r <= 0) + if (cnt_r <= 0) { + /* Nothing was buffered. Only an actual data + * report makes that a failure. */ + if (rc == WS_REKEYING) + continue; break; + } #ifdef SHELL_DEBUG buf_dump(threadCtx->channelBuffer, cnt_r); #endif @@ -1044,8 +1055,13 @@ static int ssh_worker(thread_ctx_t* threadCtx) cnt_r = wolfSSH_ChannelIdRead(ssh, agentChannelId, threadCtx->channelBuffer, sizeof threadCtx->channelBuffer); - if (cnt_r <= 0) + if (cnt_r <= 0) { + /* Nothing was buffered. Only an actual data + * report makes that a failure. */ + if (rc == WS_REKEYING) + continue; break; + } #ifdef SHELL_DEBUG buf_dump(threadCtx->channelBuffer, cnt_r); #endif @@ -1063,8 +1079,13 @@ static int ssh_worker(thread_ctx_t* threadCtx) threadCtx->fwdCtx.channelId, threadCtx->channelBuffer, sizeof threadCtx->channelBuffer); - if (cnt_r <= 0) + if (cnt_r <= 0) { + /* Nothing was buffered. Only an actual data + * report makes that a failure. */ + if (rc == WS_REKEYING) + continue; break; + } #ifdef SHELL_DEBUG buf_dump(threadCtx->channelBuffer, cnt_r); #endif @@ -1084,6 +1105,7 @@ static int ssh_worker(thread_ctx_t* threadCtx) if (fwdFd != -1) { WCLOSESOCKET(fwdFd); fwdFd = -1; + threadCtx->fwdCtx.appFd = -1; } if (threadCtx->fwdCbCtx.originName != NULL) { WFREE(threadCtx->fwdCbCtx.originName, @@ -1151,6 +1173,9 @@ static int ssh_worker(thread_ctx_t* threadCtx) if (cnt_r == 0) { /* Read zero-returned. Socket is closed. Go back to listening. */ + WCLOSESOCKET(agentFd); + agentFd = -1; + threadCtx->agentCtx.appFd = -1; threadCtx->agentCtx.state = APP_STATE_LISTEN; continue; } @@ -1164,6 +1189,9 @@ static int ssh_worker(thread_ctx_t* threadCtx) err == SOCKET_ECONNABORTED) { /* Connection reset. Socket is closed. * Go back to listening. */ + WCLOSESOCKET(agentFd); + agentFd = -1; + threadCtx->agentCtx.appFd = -1; threadCtx->agentCtx.state = APP_STATE_LISTEN; continue; } @@ -1215,6 +1243,7 @@ static int ssh_worker(thread_ctx_t* threadCtx) to listening. */ WCLOSESOCKET(fwdFd); fwdFd = -1; + threadCtx->fwdCtx.appFd = -1; if (threadCtx->fwdCbCtx.hostName != NULL) { WFREE(threadCtx->fwdCbCtx.hostName, NULL, 0); @@ -1235,6 +1264,8 @@ static int ssh_worker(thread_ctx_t* threadCtx) /* Connection reset. Socket is closed. * Go back to listening. */ WCLOSESOCKET(fwdFd); + fwdFd = -1; + threadCtx->fwdCtx.appFd = -1; threadCtx->fwdCtx.state = APP_STATE_LISTEN; continue; } From af64156a9d519a1ee5ec8027ab636f5a2767ac2a Mon Sep 17 00:00:00 2001 From: John Safranek Date: Fri, 28 Aug 2026 10:40:15 -0700 Subject: [PATCH 2/2] echoserver: only continue the rekey drain on an empty read wolfSSH_ChannelIdRead() returns a negative value for a real error, and the rekey arm treated that the same as a zero read. Restrict the continue to cnt_r == 0 so an error still ends the loop. --- examples/echoserver/echoserver.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 300ce5ff0..884418109 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -1014,7 +1014,7 @@ static int ssh_worker(thread_ctx_t* threadCtx) if (cnt_r <= 0) { /* Nothing was buffered. Only an actual data * report makes that a failure. */ - if (rc == WS_REKEYING) + if (rc == WS_REKEYING && cnt_r == 0) continue; break; } @@ -1058,7 +1058,7 @@ static int ssh_worker(thread_ctx_t* threadCtx) if (cnt_r <= 0) { /* Nothing was buffered. Only an actual data * report makes that a failure. */ - if (rc == WS_REKEYING) + if (rc == WS_REKEYING && cnt_r == 0) continue; break; } @@ -1082,7 +1082,7 @@ static int ssh_worker(thread_ctx_t* threadCtx) if (cnt_r <= 0) { /* Nothing was buffered. Only an actual data * report makes that a failure. */ - if (rc == WS_REKEYING) + if (rc == WS_REKEYING && cnt_r == 0) continue; break; }