From 10dc9605d206f30077f927ab1df9683064fa0665 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 90764c26a..54d30df9c 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -3503,9 +3503,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 a35a0c19923b9f42235f7abe20d33ef3ba0c4f35 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 54d30df9c..f357aa360 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -3103,13 +3103,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 fc1592c646096e99efb503a20099e9497f43e2e5 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 dc667cf6d020abcecc6f422cadde64283cd3c5c3 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 e8601adef0b989a8e1fdadcffa16b696923ef010 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 76918896282466c52e1f4e69cb1f0e95f8364b10 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 82485d4ccac40208a22d0c81361024e5588bc611 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 e8aa6437cbba8096e8bcfb0da23c32bb06a90e25 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 37bb07585..5d3ba2197 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 */ @@ -2007,18 +2027,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 76eabd64cf3aa075ee52b22f48ba4499dcd20845 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 5d3ba2197..19b757240 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 */ @@ -2032,14 +2114,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 e1477b7572d82da6b0ae515f4c85597993761217 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 5a5318a5fce3f01e6d7800eeecdd556ad48054de 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; }