From aad9053322089363c9e6b14236304949d50c21ea Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 14 Sep 2026 11:33:34 -0700 Subject: [PATCH 1/2] echoserver: wait to write in the SFTP loop A send the socket refused leaves WS_WANT_WRITE, and the retry branch continues past the only tcp_select() in the iteration, which watches reads. Wait on write readiness there, so a peer that has stopped reading costs a descriptor wait rather than a spin. - a buffered send with a willing socket still goes straight around - an error-ready or failed descriptor ends the loop - an interrupted select() retries instead of ending the session --- examples/echoserver/echoserver.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 2212b1e7e..7f6133fc5 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -112,12 +112,14 @@ #define SOCKET_ECONNRESET ECONNRESET #define SOCKET_ECONNABORTED ECONNABORTED #define SOCKET_EWOULDBLOCK EWOULDBLOCK + #define SOCKET_EINTR EINTR #else #include #define SOCKET_ERRNO WSAGetLastError() #define SOCKET_ECONNRESET WSAECONNRESET #define SOCKET_ECONNABORTED WSAECONNABORTED #define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK + #define SOCKET_EINTR WSAEINTR #endif #ifdef WOLFSSH_WINDOWS_CERT_STORE @@ -1567,6 +1569,16 @@ static int sftp_worker(thread_ctx_t* threadCtx) error == WS_WINDOW_FULL) ret = error; if (error == WS_WANT_WRITE || wolfSSH_SFTP_PendingSend(ssh)) { + /* The tcp_select() this skips watches reads only. */ + if (error == WS_WANT_WRITE) { + selected = tcp_select_write(s, TEST_SFTP_TIMEOUT); + /* An interrupted select() is not a dead socket. */ + if (selected == WS_SELECT_ERROR_READY + || (selected == WS_SELECT_FAIL + && SOCKET_ERRNO != SOCKET_EINTR)) { + break; + } + } continue; /* no need to spend time attempting to pull data * if there is still pending sends */ } From aa92f501f5e16457cdb9f6d64982748b73f451af Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 14 Sep 2026 11:33:34 -0700 Subject: [PATCH 2/2] scp: correct the ScpStreamRead() comment The helper drains a WS_EXTDATA and retries the read rather than handing it back, so it is no longer error-code transparent for that one status. Say so where the claim is made, and condense the rest. --- src/wolfscp.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/wolfscp.c b/src/wolfscp.c index cbbeb7044..0cb9366ab 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -171,13 +171,11 @@ static int ScpStreamSend(WOLFSSH* ssh, byte* data, word32 sz) /* Reads up to sz bytes into data, completing any rekey that fires mid-read. * - * Flushes queued output before reading so a KEXINIT enqueued by a receive-side - * highwater rekey is actually sent, otherwise the peer can wait for our KEXINIT - * while we block on the read. On a read that fails with WS_REKEYING the worker - * is driven to finish the rekey and the read is retried. The helper is - * error-code transparent: every other status (WS_EOF, WS_EXTDATA, - * WS_CHANNEL_CLOSED, WS_SOCKET_ERROR_E, WS_WANT_READ/WS_WANT_WRITE, byte count) - * is returned unchanged so each caller keeps its existing branch handling. + * Flushes queued output first, or a KEXINIT from a receive-side highwater + * rekey sits unsent while both ends block on a read. A WS_REKEYING is driven + * to completion and a WS_EXTDATA is drained, then the read retries; every + * other status passes through unchanged, so callers keep their branch + * handling. */ static int ScpStreamRead(WOLFSSH* ssh, byte* data, word32 sz) {