From 60c420b70875d389065bdfe79edd5bcb40f0ea58 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:40:55 -0700 Subject: [PATCH 01/11] wolfscp: remove dead sendCtx null re-check FindNextDirEntry() returns WS_BAD_ARGUMENT when its ctx is NULL, so by the time control reaches the second check ret is already WS_BAD_ARGUMENT whenever sendCtx is NULL. The earlier check right after the call is kept. Issue: CID-572848 --- src/wolfscp.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wolfscp.c b/src/wolfscp.c index 441549673..142363170 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -3409,9 +3409,6 @@ int wsScpSendCallback(WOLFSSH* ssh, int state, const char* peerRequest, } } - if (ret != WS_BAD_ARGUMENT && sendCtx == NULL) - ret = WS_BAD_ARGUMENT; - if (ret == WS_SUCCESS) { ret = ScpProcessEntry(ssh, fileName, mTime, aTime, fileMode, totalFileSz, buf, From 4b31335838fd4eacbed1b19944ce69b1e9696552 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:41:14 -0700 Subject: [PATCH 02/11] wolfscp: fix path length check and terminate filePath The length guard allowed dirNameLen + 1 + dNameLen to equal the buffer size, leaving no room for the terminating null, after which WSTRNCAT appends nothing and the wrong path is used. Also terminate filePath explicitly after the WSTRNCPY of dirName. Issue: CID-572864 --- src/wolfscp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wolfscp.c b/src/wolfscp.c index 142363170..2784b884f 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -3010,13 +3010,15 @@ static int ScpProcessEntry(WOLFSSH* ssh, char* fileName, word64* mTime, #else dNameLen = (int)WSTRLEN(sendCtx->entry->d_name); #endif - if ((dirNameLen + 1 + dNameLen) > DEFAULT_SCP_FILE_NAME_SZ) { + /* need room for the separator and the terminating null */ + if ((dirNameLen + 1 + dNameLen) >= DEFAULT_SCP_FILE_NAME_SZ) { WLOG(WS_LOG_ERROR, "scp: dir name length too long, abort"); ret = WS_SCP_ABORT; } else { WSTRNCPY(filePath, sendCtx->dirName, DEFAULT_SCP_FILE_NAME_SZ); + filePath[DEFAULT_SCP_FILE_NAME_SZ - 1] = '\0'; WSTRNCAT(filePath, "/", DEFAULT_SCP_FILE_NAME_SZ); #ifdef WOLFSSL_NUCLEUS From 74d604a410bce6d3f2a0585b81d9f93dfb619ad9 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:41:35 -0700 Subject: [PATCH 03/11] wolfsftp: check SFTP_SetHeader in RecvRealPath The return of SFTP_SetHeader() was ignored, unlike the other call sites. Free the output buffer on the error path only when it was allocated here rather than borrowed from the receive state. Issue: CID-572923 --- src/wolfsftp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/wolfsftp.c b/src/wolfsftp.c index c2d71a9cf..7310be2ed 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -1561,8 +1561,15 @@ static int wolfSSH_SFTP_RecvRealPath(WOLFSSH* ssh, int reqId, byte* data, return WS_MEMORY_E; } - SFTP_SetHeader(ssh, reqId, WOLFSSH_FTP_NAME, - outSz - WOLFSSH_SFTP_HEADER, out); + if (SFTP_SetHeader(ssh, reqId, WOLFSSH_FTP_NAME, + outSz - WOLFSSH_SFTP_HEADER, out) != WS_SUCCESS) { + /* only free "out" when it was allocated here, otherwise it is the + * state buffer owned by "ssh" */ + if (outSz > (word32)maxSz) { + WFREE(out, ssh->ctx->heap, DYNTYPE_BUFFER); + } + return WS_BUFFER_E; + } lidx += WOLFSSH_SFTP_HEADER; /* set number of files */ From 43a38be3b5a102f94060410483fc0f1db6684e9c Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:42:28 -0700 Subject: [PATCH 04/11] wolfsftp: check the resume seek in wolfSSH_SFTP_Put A failed WFSEEK() left the local file at offset 0 while the remote write continued from the resume offset, silently corrupting the upload. Fail out through the local close instead, the file is open at that point. Issue: CID-572932 --- src/wolfsftp.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 7310be2ed..9a52dc491 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -10053,7 +10053,15 @@ int wolfSSH_SFTP_Put(WOLFSSH* ssh, char* from, char* to, byte resume, #if SIZEOF_OFF_T == 8 offset = (((word64)state->pOfst[1]) << 32) | offset; #endif - WFSEEK(ssh->fs, state->fl, offset, 0); + if (WFSEEK(ssh->fs, state->fl, offset, 0) != 0) { + WLOG(WS_LOG_SFTP, "Unable to seek input file"); + ssh->error = WS_BAD_FILE_E; + ret = WS_FATAL_ERROR; + /* no remote handle to close yet */ + state->handleSz = 0; + state->state = STATE_PUT_CLOSE_LOCAL; + continue; + } } #else /* USE_WINDOWS_API */ state->fileHandle = WS_CreateFileA(from, GENERIC_READ, From c59f645b0d0fc7915caae8930b2755934e828c4c Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:43:09 -0700 Subject: [PATCH 05/11] apps/wolfssh: wire up the -a agent option The useAgent flag was never set, so both agent setup blocks in wolfSSH_Client were dead code. Add the -a option to the parser and carry it through the config struct, matching examples/client/client.c. Issue: CID-572857 --- apps/wolfssh/wolfssh.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/wolfssh/wolfssh.c b/apps/wolfssh/wolfssh.c index a22d8cc01..d859639de 100644 --- a/apps/wolfssh/wolfssh.c +++ b/apps/wolfssh/wolfssh.c @@ -95,9 +95,16 @@ static void ShowUsage(char* appPath) printf("%s v%s linked with wolfSSL %s\n", appName, LIBWOLFSSH_VERSION_STRING, LIBWOLFSSL_VERSION_STRING); - printf("usage: %s [-E logfile] [-G] [-l login_name] [-N] [-p port] " + printf("usage: %s " +#ifdef WOLFSSH_AGENT + "[-a] " +#endif + "[-E logfile] [-G] [-l login_name] [-N] [-p port] " "[-V] destination\n", appName); +#ifdef WOLFSSH_AGENT + printf(" -a attempt to use SSH-AGENT\n"); +#endif } @@ -726,6 +733,7 @@ struct config { char* command; word32 printConfig:1; word32 noCommand:1; + word32 useAgent:1; word16 port; }; @@ -783,8 +791,18 @@ static int config_parse_command_line(struct config* config, { int ch; - while ((ch = mygetopt(argc, argv, "E:Gl:Np:V")) != -1) { + while ((ch = mygetopt(argc, argv, +#ifdef WOLFSSH_AGENT + "a" +#endif + "E:Gl:Np:V")) != -1) { switch (ch) { + #ifdef WOLFSSH_AGENT + case 'a': + config->useAgent = 1; + break; + #endif + case 'E': config->logFile = myoptarg; break; @@ -887,6 +905,9 @@ static int config_print(struct config* config) printf("pubKeyFile %s\n", config->pubKeyFile ? config->pubKeyFile : "none"); printf("noCommand %s\n", config->noCommand ? "true" : "false"); + #ifdef WOLFSSH_AGENT + printf("useAgent %s\n", config->useAgent ? "true" : "false"); + #endif printf("logfile %s\n", config->logFile ? config->logFile : "default"); printf("command %s\n", config->command ? config->command : "none"); } @@ -950,6 +971,10 @@ static THREAD_RETURN WOLFSSH_THREAD wolfSSH_Client(void* args) ((func_args*)args)->argc, ((func_args*)args)->argv); config_print(&config); +#ifdef WOLFSSH_AGENT + useAgent = (byte)config.useAgent; +#endif + if (config.user == NULL) err_sys("client requires a username parameter."); From 7b4847564532040343c2c4e327776b46abf7401b Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:43:39 -0700 Subject: [PATCH 06/11] apps/wolfssh, examples/client: retry readInput sends over a rekey Both loop exits in readInput() returned early, leaving the per-thread ECC cache cleanup after the loop unreachable. Break out of the loop instead. wolfSSH_stream_send() reports a rekey in progress by returning WS_FATAL_ERROR with the ssh error set to WS_REKEYING, so testing the return value against WS_REKEYING never matches. Read the code with wolfSSH_get_error() and resend the same buffer once the rekey finishes, in both the app and the example client. Looping back to read() instead would drop the input already taken from stdin. Issue: CID-572833 --- apps/wolfssh/wolfssh.c | 26 ++++++++++++++++++++------ examples/client/client.c | 29 ++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/apps/wolfssh/wolfssh.c b/apps/wolfssh/wolfssh.c index d859639de..4ae621e32 100644 --- a/apps/wolfssh/wolfssh.c +++ b/apps/wolfssh/wolfssh.c @@ -402,6 +402,7 @@ static THREAD_RET readInput(void* in) int bufSz = sizeof(buf); thread_args* args = (thread_args*)in; int ret = 0; + int err = 0; word32 sz = 0; #ifdef USE_WINDOWS_API HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); @@ -420,15 +421,28 @@ static THREAD_RET readInput(void* in) #endif if (ret <= 0) { fprintf(stderr, "Error reading stdin\n"); - return THREAD_RET_SUCCESS; + break; } - /* lock SSH structure access */ - wc_LockMutex(&args->lock); - ret = wolfSSH_stream_send(args->ssh, buf, sz); - wc_UnLockMutex(&args->lock); + do { + /* lock SSH structure access */ + wc_LockMutex(&args->lock); + ret = wolfSSH_stream_send(args->ssh, buf, sz); + err = (ret == WS_FATAL_ERROR) ? + wolfSSH_get_error(args->ssh) : ret; + wc_UnLockMutex(&args->lock); + if (err == WS_REKEYING) { + /* give readPeer() the lock to finish the rekey, then + * send this buffer again */ + #ifdef USE_WINDOWS_API + Sleep(1); + #else + usleep(1000); + #endif + } + } while (err == WS_REKEYING); if (ret <= 0) { fprintf(stderr, "Couldn't send data\n"); - return THREAD_RET_SUCCESS; + break; } } #if !defined(WOLFSSH_NO_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) diff --git a/examples/client/client.c b/examples/client/client.c index 38e446721..61c79e93a 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -368,6 +368,7 @@ static THREAD_RET readInput(void* in) int bufSz = sizeof(buf); thread_args* args = (thread_args*)in; int ret = 0; + int err = 0; word32 sz = 0; #ifdef USE_WINDOWS_API HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); @@ -386,18 +387,28 @@ static THREAD_RET readInput(void* in) #endif if (ret <= 0) { fprintf(stderr, "Error reading stdin\n"); - return THREAD_RET_SUCCESS; + break; } - /* lock SSH structure access */ - wc_LockMutex(&args->lock); - ret = wolfSSH_stream_send(args->ssh, buf, sz); - wc_UnLockMutex(&args->lock); - if (ret <= 0) { - if (ret == WS_REKEYING) { - continue; + do { + /* lock SSH structure access */ + wc_LockMutex(&args->lock); + ret = wolfSSH_stream_send(args->ssh, buf, sz); + err = (ret == WS_FATAL_ERROR) ? + wolfSSH_get_error(args->ssh) : ret; + wc_UnLockMutex(&args->lock); + if (err == WS_REKEYING) { + /* give readPeer() the lock to finish the rekey, then + * send this buffer again */ + #ifdef USE_WINDOWS_API + Sleep(1); + #else + usleep(1000); + #endif } + } while (err == WS_REKEYING); + if (ret <= 0) { fprintf(stderr, "Couldn't send data\n"); - return THREAD_RET_SUCCESS; + break; } } #if !defined(WOLFSSH_NO_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) From 5074a69a547fdc784c81c435c5ff8d4725d5480f Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:44:04 -0700 Subject: [PATCH 07/11] examples/client: drop dead select_ret tests in NonBlockSSH_connect The loop condition already guarantees a want-read or want-write error, so the select_ret arms of the retry test and the else chain could never run. Retry unconditionally; tcp_select still throttles the loop. Same change applied to the copy in apps/wolfssh/wolfssh.c. Issue: CID-572884 --- apps/wolfssh/wolfssh.c | 25 +++++++------------------ examples/client/client.c | 25 +++++++------------------ 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/apps/wolfssh/wolfssh.c b/apps/wolfssh/wolfssh.c index 4ae621e32..92901b70b 100644 --- a/apps/wolfssh/wolfssh.c +++ b/apps/wolfssh/wolfssh.c @@ -119,7 +119,6 @@ static int NonBlockSSH_connect(WOLFSSH* ssh) int ret; int error; SOCKET_T sockfd; - int select_ret = 0; ret = wolfSSH_connect(ssh); error = wolfSSH_get_error(ssh); @@ -128,23 +127,13 @@ static int NonBlockSSH_connect(WOLFSSH* ssh) while (ret != WS_SUCCESS && (error == WS_WANT_READ || error == WS_WANT_WRITE)) { - select_ret = tcp_select(sockfd, 1); - - /* Continue in want write cases even if did not select on socket - * because there could be pending data to be written. Added continue - * on want write for test cases where a forced want read was introduced - * and the socket will not be receiving more data. */ - if (error == WS_WANT_WRITE || error == WS_WANT_READ || - select_ret == WS_SELECT_RECV_READY || - select_ret == WS_SELECT_ERROR_READY) - { - ret = wolfSSH_connect(ssh); - error = wolfSSH_get_error(ssh); - } - else if (select_ret == WS_SELECT_TIMEOUT) - error = WS_WANT_READ; - else - error = WS_FATAL_ERROR; + /* tcp_select only throttles the loop, always retry. On want write + * there may be pending data to send, and on want read a test case + * may have forced the want read with no more data coming in. */ + (void)tcp_select(sockfd, 1); + + ret = wolfSSH_connect(ssh); + error = wolfSSH_get_error(ssh); } return ret; diff --git a/examples/client/client.c b/examples/client/client.c index 61c79e93a..c6114118b 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -149,7 +149,6 @@ static int NonBlockSSH_connect(WOLFSSH* ssh) int ret; int error; SOCKET_T sockfd; - int select_ret = 0; ret = wolfSSH_connect(ssh); error = wolfSSH_get_error(ssh); @@ -163,23 +162,13 @@ static int NonBlockSSH_connect(WOLFSSH* ssh) else if (error == WS_WANT_WRITE) printf("... client would write block\n"); - select_ret = tcp_select(sockfd, 1); - - /* Continue in want write cases even if did not select on socket - * because there could be pending data to be written. Added continue - * on want write for test cases where a forced want read was introduced - * and the socket will not be receiving more data. */ - if (error == WS_WANT_WRITE || error == WS_WANT_READ || - select_ret == WS_SELECT_RECV_READY || - select_ret == WS_SELECT_ERROR_READY) - { - ret = wolfSSH_connect(ssh); - error = wolfSSH_get_error(ssh); - } - else if (select_ret == WS_SELECT_TIMEOUT) - error = WS_WANT_READ; - else - error = WS_FATAL_ERROR; + /* tcp_select only throttles the loop, always retry. On want write + * there may be pending data to send, and on want read a test case + * may have forced the want read with no more data coming in. */ + (void)tcp_select(sockfd, 1); + + ret = wolfSSH_connect(ssh); + error = wolfSSH_get_error(ssh); } return ret; From 59c4afe32fa142870496f8818b5a3f3bdaa67588 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:42:07 -0700 Subject: [PATCH 08/11] wolfsshd: check fcntl results in the pipe drain The leftover-data drain after waitpid ignored both fcntl calls. Check the get and the set, and skip the drain read for a pipe that could not be made non-blocking so the read cannot hang the connection process. Issue: CID-572931 --- apps/wolfsshd/wolfsshd.c | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index fb8111deb..de806a782 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1388,6 +1388,26 @@ static int SHELL_IsPty(WOLFSSH* ssh) return ret; } +/* set a descriptor to non blocking, returns 0 on success and -1 on failure */ +static int SHELL_SetNonBlocking(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + wolfSSH_Log(WS_LOG_ERROR, "[SSHD] fcntl get failed"); + return -1; + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + wolfSSH_Log(WS_LOG_ERROR, "[SSHD] fcntl set failed"); + return -1; + } + + return 0; +} + + /* handles creating a new shell env. and maintains SSH connection for incoming * user input as well as output of the shell. * return WS_SUCCESS on success */ @@ -1973,18 +1993,20 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, if (!ptyReq || forcedCmd) { int readSz; - fcntl(stdoutPipe[0], F_SETFL, fcntl(stdoutPipe[0], F_GETFL) - | O_NONBLOCK); - readSz = (int)read(stdoutPipe[0], shellBuffer, sizeof shellBuffer); - if (readSz > 0) { - wolfSSH_ChannelIdSend(ssh, shellChannelId, shellBuffer, readSz); + /* when the pipe can not be made non blocking skip the drain, a + * blocking read here could hang the connection process */ + if (SHELL_SetNonBlocking(stdoutPipe[0]) == 0) { + readSz = (int)read(stdoutPipe[0], shellBuffer, sizeof shellBuffer); + if (readSz > 0) { + wolfSSH_ChannelIdSend(ssh, shellChannelId, shellBuffer, readSz); + } } - fcntl(stderrPipe[0], F_SETFL, fcntl(stderrPipe[0], F_GETFL) - | O_NONBLOCK); - readSz = (int)read(stderrPipe[0], shellBuffer, sizeof shellBuffer); - if (readSz > 0) { - wolfSSH_extended_data_send(ssh, shellBuffer, readSz); + if (SHELL_SetNonBlocking(stderrPipe[0]) == 0) { + readSz = (int)read(stderrPipe[0], shellBuffer, sizeof shellBuffer); + if (readSz > 0) { + wolfSSH_extended_data_send(ssh, shellBuffer, readSz); + } } close(stdoutPipe[0]); From 20673aca92b8782410670663c5d88a63d1c410d3 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:43:13 -0700 Subject: [PATCH 09/11] wolfsshd: retry the final shell output flush The drain after waitpid ignored the send return, so on a non-blocking socket the tail of a command's output was dropped on a full window, a rekey or a would block. Retry a bounded number of times and log when the data still can not be sent. Issue: CID-572907 --- apps/wolfsshd/wolfsshd.c | 88 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index de806a782..b9dc1c71b 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1408,6 +1408,88 @@ static int SHELL_SetNonBlocking(int fd) } +#ifndef WOLFSSHD_SHELL_FLUSH_TRIES + #define WOLFSSHD_SHELL_FLUSH_TRIES 10 +#endif +#ifndef WOLFSSHD_SHELL_FLUSH_WAIT_US + #define WOLFSSHD_SHELL_FLUSH_WAIT_US 50000 +#endif + +/* Send out the last of a shell's output once the child has been reaped. The + * SSH socket is non blocking, so retry a bounded number of times on a full + * window, a rekey or a would block. 'ext' selects the extended (stderr) data + * stream. Returns 0 on success and -1 when the data could not all be sent. */ +static int SHELL_FlushOut(WOLFSSH* ssh, WS_SOCKET_T sshFd, word32 channelId, + byte* buf, int sz, int ext) +{ + int tries = 0; + + while (sz > 0 && tries < WOLFSSHD_SHELL_FLUSH_TRIES) { + int cnt_w; + + if (ext) { + cnt_w = wolfSSH_extended_data_send(ssh, buf, sz); + } + else { + cnt_w = wolfSSH_ChannelIdSend(ssh, channelId, buf, sz); + } + + if (cnt_w == WS_WINDOW_FULL || cnt_w == WS_REKEYING || + cnt_w == WS_WANT_WRITE) { + fd_set fds; + struct timeval to; + + FD_ZERO(&fds); + FD_SET(sshFd, &fds); + to.tv_sec = 0; + to.tv_usec = WOLFSSHD_SHELL_FLUSH_WAIT_US; + if (cnt_w == WS_WANT_WRITE) { + select((int)sshFd + 1, NULL, &fds, NULL, &to); + } + else { + /* waiting on the peer's window adjust or kex packets */ + select((int)sshFd + 1, &fds, NULL, NULL, &to); + } + + /* process what came in, otherwise the window never opens and + * the rekey never finishes, and the send can not progress */ + if (wolfSSH_worker(ssh, NULL) < 0) { + int err = wolfSSH_get_error(ssh); + + if (err != WS_WANT_READ && err != WS_WANT_WRITE && + err != WS_CHAN_RXD && err != WS_REKEYING) { + wolfSSH_Log(WS_LOG_ERROR, + "[SSHD] Issue draining connection on final flush"); + return -1; + } + } + tries++; + continue; + } + + if (cnt_w < 0) { + wolfSSH_Log(WS_LOG_ERROR, + "[SSHD] Issue sending final shell output"); + return -1; + } + + sz -= cnt_w; + if (sz > 0) { + WMEMMOVE(buf, buf + cnt_w, sz); + tries++; + } + } + + if (sz > 0) { + wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Unable to send all of the final " + "shell output, %d bytes dropped", sz); + return -1; + } + + return 0; +} + + /* handles creating a new shell env. and maintains SSH connection for incoming * user input as well as output of the shell. * return WS_SUCCESS on success */ @@ -1998,14 +2080,16 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, if (SHELL_SetNonBlocking(stdoutPipe[0]) == 0) { readSz = (int)read(stdoutPipe[0], shellBuffer, sizeof shellBuffer); if (readSz > 0) { - wolfSSH_ChannelIdSend(ssh, shellChannelId, shellBuffer, readSz); + SHELL_FlushOut(ssh, sshFd, shellChannelId, shellBuffer, readSz, + 0); } } if (SHELL_SetNonBlocking(stderrPipe[0]) == 0) { readSz = (int)read(stderrPipe[0], shellBuffer, sizeof shellBuffer); if (readSz > 0) { - wolfSSH_extended_data_send(ssh, shellBuffer, readSz); + SHELL_FlushOut(ssh, sshFd, shellChannelId, shellBuffer, readSz, + 1); } } From 2379e3836a3880ee99a91c3d48954cdc1414352c Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 13:44:11 -0700 Subject: [PATCH 10/11] wolfsshd: drop dead dCert NULL check Without WOLFSSH_SMALL_STACK dCert is the address of a stack variable, so the NULL check could never fire. Keep the check under the small stack build where the WMALLOC can actually fail. Issue: CID-573006 --- apps/wolfsshd/auth.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/wolfsshd/auth.c b/apps/wolfsshd/auth.c index 9c5909184..b03729b98 100644 --- a/apps/wolfsshd/auth.c +++ b/apps/wolfsshd/auth.c @@ -2088,20 +2088,22 @@ static int RequestAuthentication(WS_UserAuthData* authData, authData->type == WOLFSSH_USERAUTH_PUBLICKEY) { /* compare user name to UPN in certificate */ if (authData->sf.publicKey.isCert) { - DecodedCert* dCert; #ifdef WOLFSSH_SMALL_STACK + DecodedCert* dCert; + dCert = (DecodedCert*)WMALLOC(sizeof(DecodedCert), NULL, DYNTYPE_CERT); - #else - DecodedCert sdCert; - dCert = &sdCert; - #endif - if (dCert == NULL) { wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error creating cert struct"); ret = WOLFSSH_USERAUTH_INVALID_PUBLICKEY; } - else { + #else + DecodedCert sdCert; + DecodedCert* dCert = &sdCert; + #endif + + /* ret is still success unless the allocation above failed */ + if (ret == WOLFSSH_USERAUTH_SUCCESS) { wc_InitDecodedCert(dCert, authData->sf.publicKey.publicKey, authData->sf.publicKey.publicKeySz, NULL); if (wc_ParseCert(dCert, CERT_TYPE, NO_VERIFY, NULL) != 0) { From 5ac5bd422557e72bb43dcdff9646a0a0119fd45e Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 3 Aug 2026 15:22:30 -0700 Subject: [PATCH 11/11] wolfsftp: clear the handle size when the remote open fails - handleSz is the handle buffer size going into wolfSSH_SFTP_Open(), so a failed open left it non-zero and STATE_PUT_CLOSE_REMOTE closed a zeroed handle for a file that was never opened. - The stray close overwrote ret and ssh->error, hiding the real cause. --- src/wolfsftp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 9a52dc491..1ec9d6e56 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -10095,6 +10095,9 @@ int wolfSSH_SFTP_Put(WOLFSSH* ssh, char* from, char* to, byte resume, return WS_FATAL_ERROR; } WLOG(WS_LOG_SFTP, "Error getting handle"); + /* handleSz still holds the request buffer size, the + * remote file was never opened */ + state->handleSz = 0; state->state = STATE_PUT_CLOSE_LOCAL; continue; }