From cba26eb2948ab44585b7e5cb35053b31c5000a04 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 11:57:53 -0700 Subject: [PATCH 01/24] F-4582 - Use WOLFSSH_ prefix for default logging callback opt-out --- src/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/log.c b/src/log.c index 37d8fabcd..7999cb6c7 100644 --- a/src/log.c +++ b/src/log.c @@ -49,7 +49,7 @@ #endif -#ifndef WOLFSSL_NO_DEFAULT_LOGGING_CB +#ifndef WOLFSSH_NO_DEFAULT_LOGGING_CB static void DefaultLoggingCb(enum wolfSSH_LogLevel level, const char *const msgStr); static wolfSSH_LoggingCb logFunction = DefaultLoggingCb; From d0c911d0300ea242aa2e5d034bc395a4bcf5790e Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 11:58:39 -0700 Subject: [PATCH 02/24] F-2869 - Raise default DH-GEX client minimum to 2048 bits --- wolfssh/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfssh/internal.h b/wolfssh/internal.h index a37e88514..69da51446 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -511,7 +511,7 @@ enum NameIdType { #define WOLFSSH_MAX_NAMELIST_CNT 64 #endif #ifndef WOLFSSH_DEFAULT_GEXDH_MIN - #define WOLFSSH_DEFAULT_GEXDH_MIN 1024 + #define WOLFSSH_DEFAULT_GEXDH_MIN 2048 #endif #ifndef WOLFSSH_DEFAULT_GEXDH_PREFERRED #define WOLFSSH_DEFAULT_GEXDH_PREFERRED 3072 From 927760b72d10bd8bb6ca969a861fa74e99c5181f Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 11:59:59 -0700 Subject: [PATCH 03/24] F-6268 - Fix Windows SFTP GET resume check to test gOfst elements --- src/wolfsftp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 9d4671834..11b5dd5eb 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -9652,7 +9652,7 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from, #elif defined(USE_WINDOWS_API) { DWORD desiredAccess = GENERIC_WRITE; - if (state->gOfst > 0) + if (state->gOfst[0] > 0 || state->gOfst[1] > 0) desiredAccess |= FILE_APPEND_DATA; state->fileHandle = WS_CreateFileA(to, desiredAccess, (FILE_SHARE_DELETE | FILE_SHARE_READ | From fbe0cd9ee3e7b339eee02998bae7b90fed600a90 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:00:35 -0700 Subject: [PATCH 04/24] F-4592 - Bound agent FindKeyId keyBlob compare by stored length --- src/agent.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/agent.c b/src/agent.c index 7b7ac5c5d..c3b987617 100644 --- a/src/agent.c +++ b/src/agent.c @@ -670,9 +670,11 @@ static WOLFSSH_AGENT_ID* FindKeyId(WOLFSSH_AGENT_ID* id, wc_Sha256Free(&sha); if (ret == WS_SUCCESS) { + /* only compare equal-length blobs to avoid an over-read */ while (id != NULL && WMEMCMP(digest, id->id, WC_SHA256_DIGEST_SIZE) != 0 && - WMEMCMP(keyBlob, id->keyBlob, keyBlobSz)) { + (keyBlobSz != id->keyBlobSz || + WMEMCMP(keyBlob, id->keyBlob, keyBlobSz) != 0)) { id = id->next; } } From 078f0af262479b3c6eba5d2e9b86614ba5408d23 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:00:54 -0700 Subject: [PATCH 05/24] F-6693 - Reject zero-length agent message payload in DoMessage --- src/agent.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/agent.c b/src/agent.c index c3b987617..52f23927f 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1388,7 +1388,8 @@ static int DoMessage(WOLFSSH_AGENT_CTX* agent, ato32(buf + begin, &payloadSz); WLOG(WS_LOG_AGENT, "payloadSz = %u", payloadSz); begin += LENGTH_SZ; - if (payloadSz > len - begin) { + /* reject 0: payloadSz - 1 is used as a length below */ + if (payloadSz == 0 || payloadSz > len - begin) { ret = WS_OVERFLOW_E; } } From b93fc3dd49def332195778a8a98d094b0974bc04 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:01:48 -0700 Subject: [PATCH 06/24] F-1684 - Do not log agent passphrase and zeroize it after use --- src/agent.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/agent.c b/src/agent.c index 52f23927f..0f8bae6f5 100644 --- a/src/agent.c +++ b/src/agent.c @@ -383,7 +383,8 @@ static int PostLock(WOLFSSH_AGENT_CTX* agent, WMEMCPY(pp, passphrase, ppSz); pp[ppSz] = 0; - WLOG(WS_LOG_AGENT, "Locking with passphrase '%s'", pp); + WLOG(WS_LOG_AGENT, "Locking with passphrase"); + ForceZero(pp, sizeof(pp)); return WS_SUCCESS; } @@ -406,7 +407,8 @@ static int PostUnlock(WOLFSSH_AGENT_CTX* agent, WMEMCPY(pp, passphrase, ppSz); pp[ppSz] = 0; - WLOG(WS_LOG_AGENT, "Unlocking with passphrase '%s'", pp); + WLOG(WS_LOG_AGENT, "Unlocking with passphrase"); + ForceZero(pp, sizeof(pp)); return WS_SUCCESS; } From 43d58537a3f962a44b95c8ebd6e1d71d9c1d5da8 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:02:16 -0700 Subject: [PATCH 07/24] F-2082 - Zeroize agent private key buffer with ForceZero before free --- src/agent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent.c b/src/agent.c index 0f8bae6f5..5c540198a 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1587,7 +1587,7 @@ void wolfSSH_AGENT_ID_free(WOLFSSH_AGENT_ID* id, void* heap) if (id != NULL) { if (id->keyBuffer != NULL) { - WMEMSET(id->keyBuffer, 0, id->keyBufferSz); + ForceZero(id->keyBuffer, id->keyBufferSz); WFREE(id->keyBuffer, heap, DYNTYPE_STRING); } WMEMSET(id, 0, sizeof(WOLFSSH_AGENT_ID)); From 7f065e2165080c6dfbd7f08ef522f1d13c1c1731 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:02:42 -0700 Subject: [PATCH 08/24] F-6694 - Return error instead of hanging in AGENT_worker on NULL ssh --- src/agent.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/agent.c b/src/agent.c index 5c540198a..83cbf55bf 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1687,14 +1687,10 @@ int wolfSSH_AGENT_worker(WOLFSSH* ssh) if (ssh == NULL) ret = WS_SSH_NULL_E; - while (1) { - if (ret == WS_SUCCESS) { - - SendSuccess(NULL); - DoMessage(NULL, NULL, 0, NULL); - ssh->agent->state = AGENT_STATE_DONE; - break; - } + if (ret == WS_SUCCESS) { + SendSuccess(NULL); + DoMessage(NULL, NULL, 0, NULL); + ssh->agent->state = AGENT_STATE_DONE; } WLOG_LEAVE(ret); From 5b0a8f695ff884116b8f34032f7abecb21b0beda Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:04:37 -0700 Subject: [PATCH 09/24] F-3881 - Reserve separator and NUL in RealPath segment bound check --- src/ssh.c | 13 +++++++------ tests/api.c | 2 ++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ssh.c b/src/ssh.c index f73754e92..fc9da2aa9 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -3835,15 +3835,16 @@ int wolfSSH_RealPath(const char* defaultPath, char* in, } /* Everything else is copied */ else { - if (curSz >= outSz - segSz) { - return WS_INVALID_PATH_E; - } - + /* WSTRNCAT size arg is the whole buffer; NULL if seg won't fit */ if (curSz != 1) { - WSTRNCAT(out, "/", outSz - curSz); + if (WSTRNCAT(out, "/", outSz) == NULL) { + return WS_INVALID_PATH_E; + } curSz++; } - WSTRNCAT(out, seg, outSz - curSz); + if (WSTRNCAT(out, seg, outSz) == NULL) { + return WS_INVALID_PATH_E; + } curSz += segSz; } } diff --git a/tests/api.c b/tests/api.c index 54959cbd3..a2bca55cc 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3336,6 +3336,8 @@ struct RealPathTestFailCase realPathFail[] = { { "12345678", "12345678", 8, WS_INVALID_PATH_E }, /* Copy segment will not fit in output. */ { "1234567", "12345678", 8, WS_INVALID_PATH_E }, + /* Separator plus segment must leave room for the NUL. */ + { NULL, "aaa/bbb", 8, WS_INVALID_PATH_E }, }; static void DoRealPathTestFailCase(struct RealPathTestFailCase* tc) From 6c4a0e107c5afe7b8406f7ea727aa08a03478bc9 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:05:39 -0700 Subject: [PATCH 10/24] F-5852 - Only treat /.. as parent traversal in CleanPath --- src/internal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 8b7fd28cc..c04571bdb 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19705,8 +19705,9 @@ int wolfSSH_CleanPath(WOLFSSH* ssh, char* in, int inSz) if (path[i] == WS_DELIM) { int z; - /* if next two chars are .. then delete */ - if (path[i+1] == '.' && path[i+2] == '.') { + /* delete only a real ".." segment, not "..name" */ + if (path[i+1] == '.' && path[i+2] == '.' && + (path[i+3] == WS_DELIM || path[i+3] == '\0')) { enIdx = i + 3; /* start at one char before / and retrace path */ From 1cfa165b83ea4d95a02b6457e72296323d9508ac Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:06:23 -0700 Subject: [PATCH 11/24] F-4795 - Guard ClientPublicKeyCheck against short public key blob --- apps/wolfssh/common.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/wolfssh/common.c b/apps/wolfssh/common.c index ac1adc4e0..2d6e7693d 100644 --- a/apps/wolfssh/common.c +++ b/apps/wolfssh/common.c @@ -392,11 +392,16 @@ int ClientPublicKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx) fp[0] = 0; /* Get the key type out of the key. */ - ato32(pubKey, &sz); - if ((sz > pubKeySz - sizeof(word32)) - || (sz > WOLFSSH_CLIENT_PUBKEYTYPE_SIZE_ESTIMATE - 1)) { + if (pubKeySz < sizeof(word32)) { ret = -1; } + else { + ato32(pubKey, &sz); + if ((sz > pubKeySz - sizeof(word32)) + || (sz > WOLFSSH_CLIENT_PUBKEYTYPE_SIZE_ESTIMATE - 1)) { + ret = -1; + } + } } if (ret == 0) { From 32ac2650891b3d6863a2e3aa50cd7df1c8bdf19e Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:07:19 -0700 Subject: [PATCH 12/24] F-3210 - Null-check command allocation in client argument parsing --- apps/wolfssh/wolfssh.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/wolfssh/wolfssh.c b/apps/wolfssh/wolfssh.c index f9414d795..63cb5c635 100644 --- a/apps/wolfssh/wolfssh.c +++ b/apps/wolfssh/wolfssh.c @@ -857,6 +857,10 @@ static int config_parse_command_line(struct config* config, } command = (char*)WMALLOC(commandSz, NULL, 0); + if (command == NULL) { + fprintf(stderr, "Couldn't capture the command.\n"); + exit(EXIT_FAILURE); + } config->command = command; cursor = command; From 9dfe1dce74efa01828be8a95e6a622d0cbcf36ab Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:07:59 -0700 Subject: [PATCH 13/24] F-4583 - Remove vestigial zero-length read in echoserver global_req --- examples/echoserver/echoserver.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index f86fbc75f..53817f5d8 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -299,7 +299,6 @@ static void *global_req(void *ctx) int ret; const char str[] = "SampleRequest"; thread_ctx_t *threadCtx = (thread_ctx_t *)ctx; - byte buf[0]; wolfSSH_SetReqSuccess(threadCtx->ctx, callbackReqSuccess); wolfSSH_SetReqSuccessCtx(threadCtx->ssh, &threadCtx->ssh); /* dummy ctx */ @@ -318,14 +317,6 @@ static void *global_req(void *ctx) wolfSSH_shutdown(threadCtx->ssh); return NULL; } - - wolfSSH_stream_read(threadCtx->ssh, buf, 0); - if (ret != WS_SUCCESS) - { - printf("wolfSSH_stream_read Failed.\n"); - wolfSSH_shutdown(threadCtx->ssh); - return NULL; - } } return NULL; } From 80d90e76c0d8be6b6fb4cd3a40fdc44280edff4a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:09:25 -0700 Subject: [PATCH 14/24] F-3446 - Bound default password copy to userPassword buffer size --- examples/client/common.c | 2 ++ examples/portfwd/portfwd.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/client/common.c b/examples/client/common.c index ed7e33d7d..8f3d37652 100644 --- a/examples/client/common.c +++ b/examples/client/common.c @@ -519,6 +519,8 @@ int ClientUserAuth(byte authType, else if (authType == WOLFSSH_USERAUTH_PASSWORD) { if (defaultPassword != NULL) { passwordSz = (word32)strlen(defaultPassword); + if (passwordSz > (word32)sizeof(userPassword)) + passwordSz = (word32)sizeof(userPassword); memcpy(userPassword, defaultPassword, passwordSz); } #ifdef WOLFSSH_TERM diff --git a/examples/portfwd/portfwd.c b/examples/portfwd/portfwd.c index 355e5adb4..86521e94e 100644 --- a/examples/portfwd/portfwd.c +++ b/examples/portfwd/portfwd.c @@ -172,6 +172,8 @@ static int wsUserAuth(byte authType, (void)authType; if (defaultPassword != NULL) { passwordSz = (word32)strlen(defaultPassword); + if (passwordSz > (word32)sizeof(userPassword)) + passwordSz = (word32)sizeof(userPassword); memcpy(userPassword, defaultPassword, passwordSz); } else { From 40e5365092eb2126bb65e9d244755662806a8992 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:10:20 -0700 Subject: [PATCH 15/24] F-1278 - Zeroize ML-KEM client private key after decapsulation --- src/internal.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/internal.c b/src/internal.c index c04571bdb..20c70edc6 100644 --- a/src/internal.c +++ b/src/internal.c @@ -6666,6 +6666,8 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, wc_MlKemKey_Free(&kem); + ForceZero(ssh->handshake->x, ssh->handshake->xSz); + /* Replace the concatenated shared secrets with the hash. That * will become the new shared secret. */ if (ret == 0) { From 92bfa1f9cc5d8cb245bab7882bbbba67a0422158 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:11:50 -0700 Subject: [PATCH 16/24] F-3453 - Free and zeroize host key DER on key-count error path --- src/internal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/internal.c b/src/internal.c index 20c70edc6..2e6ae8377 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2794,6 +2794,9 @@ static int SetHostPrivateKey(WOLFSSH_CTX* ctx, } if (destIdx >= WOLFSSH_MAX_PVT_KEYS) { + /* der not taken on this path; free it to avoid a leak */ + ForceZero(der, derSz); + WFREE(der, ctx->heap, dynamicType); ret = WS_CTX_KEY_COUNT_E; } else { From c22e219bd12bbaf048afccc8879ef0c4c9190c79 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:13:36 -0700 Subject: [PATCH 17/24] F-6277 - Zeroize SFTP GET and PUT file buffers before freeing state --- src/wolfsftp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 11b5dd5eb..28abf9dcb 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -850,6 +850,7 @@ static void wolfSSH_SFTP_ClearState(WOLFSSH* ssh, enum WS_SFTP_STATE_ID state) if (state & STATE_ID_GET) { if (ssh->getState) { + ForceZero(ssh->getState->r, WOLFSSH_MAX_SFTP_RW); WFREE(ssh->getState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->getState = NULL; } @@ -928,6 +929,7 @@ static void wolfSSH_SFTP_ClearState(WOLFSSH* ssh, enum WS_SFTP_STATE_ID state) if (state & STATE_ID_PUT) { if (ssh->putState) { + ForceZero(ssh->putState->r, WOLFSSH_MAX_SFTP_RW); WFREE(ssh->putState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->putState = NULL; } @@ -9775,6 +9777,7 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from, case STATE_GET_CLEANUP: WLOG(WS_LOG_SFTP, "SFTP GET STATE: CLEANUP"); if (ssh->getState != NULL) { + ForceZero(ssh->getState->r, WOLFSSH_MAX_SFTP_RW); WFREE(ssh->getState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->getState = NULL; } @@ -9998,6 +10001,7 @@ int wolfSSH_SFTP_Put(WOLFSSH* ssh, char* from, char* to, byte resume, case STATE_PUT_CLEANUP: WLOG(WS_LOG_SFTP, "SFTP PUT STATE: CLEANUP"); if (ssh->putState != NULL) { + ForceZero(ssh->putState->r, WOLFSSH_MAX_SFTP_RW); WFREE(ssh->putState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->putState = NULL; } From b57aac808f897ef96a5733d2e2f70e7e81c59d52 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:16:35 -0700 Subject: [PATCH 18/24] F-4794 - Bound CSI control sequence read and reset escape state --- src/wolfterm.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wolfterm.c b/src/wolfterm.c index 7ddd5d117..f2e473058 100644 --- a/src/wolfterm.c +++ b/src/wolfterm.c @@ -473,6 +473,15 @@ static int wolfSSH_DoControlSeq(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf, } else { numArgs = getArgs(buf, bufSz, &i, args); + if (i >= bufSz) { + /* save left overs for next call */ + if (bufSz - *idx > WOLFSSL_MAX_ESCBUF) { + return WS_FATAL_ERROR; + } + WMEMCPY(ssh->escBuf, buf + *idx, bufSz - *idx); + ssh->escBufSz = bufSz - *idx; + return WS_WANT_READ; + } c = buf[i]; i++; } } @@ -669,6 +678,7 @@ int wolfSSH_ConvertConsole(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf, if (ret == WS_WANT_READ) { return ret; } + ssh->escState = WC_ESC_NONE; ssh->escBufSz = 0; break; From 9a162b98628143554b2888ae42a9c222924755f5 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:16:55 -0700 Subject: [PATCH 19/24] F-4106 - Write only the newline byte to console, not the NUL --- src/wolfterm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wolfterm.c b/src/wolfterm.c index f2e473058..dc377d6af 100644 --- a/src/wolfterm.c +++ b/src/wolfterm.c @@ -747,7 +747,7 @@ int wolfSSH_ConvertConsole(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf, case 'D': /* linefeed */ - if (WS_WRITECONSOLE(handle, "\n", sizeof("\n"), &wrt, NULL) + if (WS_WRITECONSOLE(handle, "\n", 1, &wrt, NULL) == 0) { WLOG(WS_LOG_DEBUG, "Error writing newline to handle"); return WS_FATAL_ERROR; @@ -755,7 +755,7 @@ int wolfSSH_ConvertConsole(WOLFSSH* ssh, WOLFSSH_HANDLE handle, byte* buf, break; case 'E': /* newline */ - if (WS_WRITECONSOLE(handle, "\n", sizeof("\n"), &wrt, NULL) + if (WS_WRITECONSOLE(handle, "\n", 1, &wrt, NULL) == 0) { WLOG(WS_LOG_DEBUG, "Error writing newline to handle"); return WS_FATAL_ERROR; From 5be89901523eaf216521518a919349b19380c8b6 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:17:59 -0700 Subject: [PATCH 20/24] F-4105 - Write only bytes read to Windows stdout handle --- apps/wolfssh/wolfssh.c | 2 +- examples/client/client.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/wolfssh/wolfssh.c b/apps/wolfssh/wolfssh.c index 63cb5c635..a22d8cc01 100644 --- a/apps/wolfssh/wolfssh.c +++ b/apps/wolfssh/wolfssh.c @@ -563,7 +563,7 @@ static THREAD_RET readPeer(void* in) buf[bufSz - 1] = '\0'; #ifdef USE_WINDOWS_API - if (WriteFile(stdoutHandle, buf, bufSz, &writtn, NULL) == FALSE) { + if (WriteFile(stdoutHandle, buf, (DWORD)ret, &writtn, NULL) == FALSE) { err_sys("Failed to write to stdout handle"); } #else diff --git a/examples/client/client.c b/examples/client/client.c index 5ab01b19a..38e446721 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -546,7 +546,7 @@ static THREAD_RET readPeer(void* in) buf[bufSz - 1] = '\0'; #ifdef USE_WINDOWS_API - if (WriteFile(stdoutHandle, buf, bufSz, &writtn, NULL) == FALSE) { + if (WriteFile(stdoutHandle, buf, (DWORD)ret, &writtn, NULL) == FALSE) { err_sys("Failed to write to stdout handle"); } #else From 8c411f428f87905f193b3539a8835e7982662f63 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:20:16 -0700 Subject: [PATCH 21/24] F-4108 - Build full path and check lstat return in QNX Include scan --- apps/wolfsshd/configuration.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/wolfsshd/configuration.c b/apps/wolfsshd/configuration.c index c16c63b16..324c455ab 100644 --- a/apps/wolfsshd/configuration.c +++ b/apps/wolfsshd/configuration.c @@ -787,9 +787,13 @@ static int HandleInclude(WOLFSSHD_CONFIG *conf, const char *value, int depth) /* Skip sub-directories */ #if defined(__QNX__) || defined(__QNXNTO__) struct stat s; + int pathLen; - lstat(dir->d_name, &s); - if (!S_ISDIR(s.st_mode)) + pathLen = WSNPRINTF(filepath, PATH_MAX, "%s/%s", + path, dir->d_name); + if (pathLen > 0 && pathLen < PATH_MAX && + lstat(filepath, &s) == 0 && + !S_ISDIR(s.st_mode)) #else if (dir->d_type != DT_DIR) #endif @@ -819,9 +823,13 @@ static int HandleInclude(WOLFSSHD_CONFIG *conf, const char *value, int depth) /* Skip sub-directories */ #if defined(__QNX__) || defined(__QNXNTO__) struct stat s; + int pathLen; - lstat(dir->d_name, &s); - if (!S_ISDIR(s.st_mode)) + pathLen = WSNPRINTF(filepath, PATH_MAX, "%s/%s", + path, dir->d_name); + if (pathLen > 0 && pathLen < PATH_MAX && + lstat(filepath, &s) == 0 && + !S_ISDIR(s.st_mode)) #else if (dir->d_type != DT_DIR) #endif From 1ea8ed2500ff52c6da47b55a4aadf406243895ec Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:23:01 -0700 Subject: [PATCH 22/24] F-5900 - Require SHA-256+ signature algorithm in FPKI CheckProfile --- src/certman.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/certman.c b/src/certman.c index 1352b37d0..f5a745cd9 100644 --- a/src/certman.c +++ b/src/certman.c @@ -560,6 +560,18 @@ static int CheckProfile(DecodedCert* cert, int profile) } } + if (valid) { + valid = + cert->signatureOID == CTC_SHA256wRSA || + cert->signatureOID == CTC_SHA384wRSA || + cert->signatureOID == CTC_SHA512wRSA || + cert->signatureOID == CTC_SHA256wECDSA || + cert->signatureOID == CTC_SHA384wECDSA || + cert->signatureOID == CTC_SHA512wECDSA; + if (valid != 1) + WLOG(WS_LOG_CERTMAN, "cert signature algorithm not FPKI approved"); + } + #ifdef DEBUG_WOLFSSH switch (profile) { case PROFILE_FPKI_WORKSHEET_6: From 790dfb417baf8166dcca6a6a41af3ca3bdda1ccd Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:27:18 -0700 Subject: [PATCH 23/24] F-6698 - Bound SFTP handle length against received packet size --- src/wolfsftp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 28abf9dcb..49e250960 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -7280,7 +7280,9 @@ static int wolfSSH_SFTP_GetHandle(WOLFSSH* ssh, byte* handle, word32* handleSz) * max size */ wolfSSH_SFTP_buffer_rewind(&state->buffer); if (wolfSSH_SFTP_buffer_ato32(&state->buffer, &sz) != WS_SUCCESS - || sz > WOLFSSH_MAX_HANDLE || *handleSz < sz) { + || sz > WOLFSSH_MAX_HANDLE || *handleSz < sz + || UINT32_SZ + sz + > wolfSSH_SFTP_buffer_size(&state->buffer)) { WLOG(WS_LOG_SFTP, "Handle size found was too big"); WLOG(WS_LOG_SFTP, "Check size set in input handleSz"); ssh->error = WS_BUFFER_E; From b99e75c5b1dc3e3b0ac99806ee42c96c57836a1a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:37:10 -0700 Subject: [PATCH 24/24] F-6703 - Add AES-GCM tamper rejection test for DoReceive --- tests/unit.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/tests/unit.c b/tests/unit.c index 536999c14..de6d78c15 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -37,6 +37,7 @@ #include #include #include +#include #ifndef WOLFSSH_NO_RSA #include #include @@ -868,7 +869,8 @@ static int test_MlDsaKeyGen(void) (!defined(WOLFSSH_NO_HMAC_SHA1) || \ !defined(WOLFSSH_NO_HMAC_SHA1_96) || \ !defined(WOLFSSH_NO_HMAC_SHA2_256) || \ - !defined(WOLFSSH_NO_HMAC_SHA2_512)) + !defined(WOLFSSH_NO_HMAC_SHA2_512) || \ + !defined(WOLFSSH_NO_AES_GCM)) /* Minimal SSH binary packet: uint32 length, padding_length, msgId, padding. * Same layout as tests/regress.c BuildPacket (8-byte aligned body). */ @@ -889,8 +891,15 @@ static word32 BuildMacTestPacketPrefix(byte msgId, byte* out, word32 outSz) WMEMSET(out + 6, 0, padLen); return need; } +#endif +#if defined(WOLFSSH_TEST_INTERNAL) && \ + (!defined(WOLFSSH_NO_HMAC_SHA1) || \ + !defined(WOLFSSH_NO_HMAC_SHA1_96) || \ + !defined(WOLFSSH_NO_HMAC_SHA2_256) || \ + !defined(WOLFSSH_NO_HMAC_SHA2_512)) + static int test_DoReceive_VerifyMacFailure(void) { WOLFSSH_CTX* ctx = NULL; @@ -1016,6 +1025,111 @@ static int test_DoReceive_VerifyMacFailure(void) #endif /* WOLFSSH_TEST_INTERNAL && any HMAC SHA variant enabled */ +#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_GCM) +/* Verify DoReceive rejects an AES-GCM record whose ciphertext has been + * tampered with, so the AEAD tag check fails and the connection is torn down. + * Mirrors test_DoReceive_VerifyMacFailure for the AEAD path. */ +static int test_DoReceive_AeadTagFailure(void) +{ + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + Aes encAes; + int ret; + int result = 0; + int aesInited = 0; + byte key[AES_256_KEY_SIZE]; + byte iv[GCM_NONCE_MID_SZ]; + byte pkt[UINT32_SZ + 8]; + byte record[UINT32_SZ + 8 + AES_BLOCK_SIZE]; + word32 prefixLen, payloadSz, totalLen; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) + return -220; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { + wolfSSH_CTX_free(ctx); + return -221; + } + + WMEMSET(key, 0x5A, sizeof(key)); + WMEMSET(iv, 0x31, sizeof(iv)); + + prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, pkt, sizeof(pkt)); + if (prefixLen == 0) { + result = -222; + goto done; + } + payloadSz = prefixLen - UINT32_SZ; + + ret = wc_AesInit(&encAes, NULL, INVALID_DEVID); + if (ret != 0) { + result = -223; + goto done; + } + aesInited = 1; + if (wc_AesGcmSetKey(&encAes, key, sizeof(key)) != 0) { + result = -224; + goto done; + } + WMEMCPY(record, pkt, UINT32_SZ); + if (wc_AesGcmEncrypt(&encAes, record + UINT32_SZ, pkt + UINT32_SZ, + payloadSz, iv, sizeof(iv), record + UINT32_SZ + payloadSz, + AES_BLOCK_SIZE, record, UINT32_SZ) != 0) { + result = -225; + goto done; + } + totalLen = UINT32_SZ + payloadSz + AES_BLOCK_SIZE; + + /* Tamper with a ciphertext byte so the tag check must fail. */ + record[UINT32_SZ] ^= 0x01; + + if (wc_AesInit(&ssh->decryptCipher.aes, ssh->ctx->heap, INVALID_DEVID) != 0 + || wc_AesGcmSetKey(&ssh->decryptCipher.aes, key, sizeof(key)) != 0) { + result = -226; + goto done; + } + ssh->decryptCipher.isInit = 1; + ssh->decryptCipher.cipherType = ID_AES256_GCM; + ssh->peerEncryptId = ID_AES256_GCM; + ssh->peerAeadMode = 1; + ssh->peerBlockSz = UINT32_SZ; + ssh->peerMacSz = AES_BLOCK_SIZE; + WMEMCPY(ssh->peerKeys.iv, iv, sizeof(iv)); + ssh->peerKeys.ivSz = sizeof(iv); + ssh->curSz = 0; + ssh->processReplyState = PROCESS_INIT; + ssh->error = 0; + + ShrinkBuffer(&ssh->inputBuffer, 1); + if (GrowBuffer(&ssh->inputBuffer, totalLen) != WS_SUCCESS) { + result = -227; + goto done; + } + WMEMCPY(ssh->inputBuffer.buffer, record, totalLen); + ssh->inputBuffer.length = totalLen; + ssh->inputBuffer.idx = 0; + + ret = wolfSSH_TestDoReceive(ssh); + if (ret != WS_FATAL_ERROR) { + result = -228; + goto done; + } + if (ssh->error != AES_GCM_AUTH_E) { + result = -229; + goto done; + } + +done: + if (aesInited) + wc_AesFree(&encAes); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + return result; +} +#endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_AES_GCM */ + + #ifdef WOLFSSH_TEST_INTERNAL /* Verify DoReceive rejects a binary packet whose padding_length is below the * RFC 4253 section 6 minimum of four bytes, returning WS_BUFFER_E. The packet @@ -7639,6 +7753,13 @@ int wolfSSH_UnitTest(int argc, char** argv) testResult = testResult || unitResult; #endif +#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_GCM) + unitResult = test_DoReceive_AeadTagFailure(); + printf("DoReceiveAeadTag: %s\n", + (unitResult == 0 ? "SUCCESS" : "FAILED")); + testResult = testResult || unitResult; +#endif + #ifdef WOLFSSH_TEST_INTERNAL unitResult = test_DoReceive_RejectsShortPadding(); printf("DoReceiveRejectsShortPadding: %s\n",