From d840f842027c89f6d498e3a662ea1c2c7d973e5a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 11:57:53 -0700 Subject: [PATCH 01/22] 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 7600be0707bc04faa60c07d939d55849bd6ace9f Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 11:59:59 -0700 Subject: [PATCH 02/22] 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 1d135eb30..b43464afa 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -9654,7 +9654,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 5c468144f790851decbdc65d2ad3a6eafe6f182d Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:00:35 -0700 Subject: [PATCH 03/22] F-4592 - Bound agent FindKeyId keyBlob compare by stored length --- src/agent.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/agent.c b/src/agent.c index b83c2670a..ea0c8e683 100644 --- a/src/agent.c +++ b/src/agent.c @@ -687,6 +687,7 @@ 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 && (id->keyBlobSz != keyBlobSz || From ecebe6150b25df15e1247aca0c93cb92aade83bd Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:00:54 -0700 Subject: [PATCH 04/22] 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 ea0c8e683..b3dcc824b 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1407,7 +1407,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 818d1197589a260f7fa4bfd817187c4686b6afb5 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:02:16 -0700 Subject: [PATCH 05/22] 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 b3dcc824b..18eef0482 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1604,7 +1604,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); + WS_FORCEZERO(id->keyBuffer, id->keyBufferSz); WFREE(id->keyBuffer, heap, DYNTYPE_STRING); } WMEMSET(id, 0, sizeof(WOLFSSH_AGENT_ID)); From 48cd39e65b17448bff5e87d004990cb3116aab73 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:02:42 -0700 Subject: [PATCH 06/22] 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 18eef0482..5eac61788 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1704,14 +1704,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 9896f5d688c550eda38182666af275857470406d Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:04:37 -0700 Subject: [PATCH 07/22] F-3881 - Reserve separator and NUL in RealPath segment bound check --- tests/api.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/api.c b/tests/api.c index b2e1adb72..7049b67d4 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3390,6 +3390,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 d52f978e6fd78c4ba5029928921ad931bcc8fe58 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:05:39 -0700 Subject: [PATCH 08/22] 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 098d4c6a1..0c2a5eb75 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19885,8 +19885,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 8960fe2040988431a33d68e2b4ce8c1b0c044407 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:06:23 -0700 Subject: [PATCH 09/22] 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 c6df30e62ac9ad3e7b7f4e1ae51b08dd28bfe8f3 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:07:19 -0700 Subject: [PATCH 10/22] 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 d91c3aa45cd4a19ad09b8e795f0b989f6727980a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:07:59 -0700 Subject: [PATCH 11/22] 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 20f4eb27c..8ce9c1528 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 f595a1e72a43789240915f21484a48f93639cc77 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:09:25 -0700 Subject: [PATCH 12/22] 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 13ecbfb03..4fa7c6d86 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 3900ae61fb31a9dee116f67984b41f2f63006085 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:10:20 -0700 Subject: [PATCH 13/22] 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 0c2a5eb75..b23d64ffe 100644 --- a/src/internal.c +++ b/src/internal.c @@ -6678,6 +6678,8 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, wc_MlKemKey_Free(&kem); + WS_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 14d86c272f90873493721278ffd75ef6eee8f021 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:11:50 -0700 Subject: [PATCH 14/22] 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 b23d64ffe..04b58deec 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 */ + WS_FORCEZERO(der, derSz); + WFREE(der, ctx->heap, dynamicType); ret = WS_CTX_KEY_COUNT_E; } else { From 4304fee809e8a229a160d5d054221e84afd0e7d7 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:13:36 -0700 Subject: [PATCH 15/22] 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 b43464afa..8a1dc8585 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) { + WS_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) { + WS_FORCEZERO(ssh->putState->r, WOLFSSH_MAX_SFTP_RW); WFREE(ssh->putState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->putState = NULL; } @@ -9777,6 +9779,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) { + WS_FORCEZERO(ssh->getState->r, WOLFSSH_MAX_SFTP_RW); WFREE(ssh->getState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->getState = NULL; } @@ -10000,6 +10003,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) { + WS_FORCEZERO(ssh->putState->r, WOLFSSH_MAX_SFTP_RW); WFREE(ssh->putState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->putState = NULL; } From 218030e11186687fb23c882cb8af10ccb074b7b1 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:16:35 -0700 Subject: [PATCH 16/22] 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 6dbe87985734473e7bb8257dae775052ee7784e5 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:16:55 -0700 Subject: [PATCH 17/22] 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 77f9314cdcb7c7e20ef82199dc4af77a8151541f Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:17:59 -0700 Subject: [PATCH 18/22] 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 7628e843a498ceb7b435baf7890c8717c5261684 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:20:16 -0700 Subject: [PATCH 19/22] 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 edb1bf7d32bd066ef15d4690f00a8f07f7b8ebd0 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:23:01 -0700 Subject: [PATCH 20/22] 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 e0bf154f9..8f4edd5e1 100644 --- a/src/certman.c +++ b/src/certman.c @@ -562,6 +562,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 5c11893fe9739be08ba31fe0a4546784d28fd265 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:27:18 -0700 Subject: [PATCH 21/22] 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 8a1dc8585..656306767 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 fc892652b827a17fea4d1f7a9d321108adb31304 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 12:37:10 -0700 Subject: [PATCH 22/22] 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 c6601d755..b6cad44d2 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 @@ -8788,6 +8902,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",