diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index 89b48d3a6..f9ab6597d 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -43,13 +43,12 @@ jobs: working-directory: ${{env.GITHUB_WORKSPACE}}wolfssl run: nuget restore ${{env.WOLFSSL_SOLUTION_FILE_PATH}} - - name: updated user_settings.h for sshd and x509 + - name: Update user_settings.h for sshd and x509 working-directory: ${{env.GITHUB_WORKSPACE}} - run: cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} - - - name: replace wolfSSL user_settings.h with wolfSSH user_settings.h - working-directory: ${{env.GITHUB_WORKSPACE}} - run: get-content ${{env.USER_SETTINGS_H_NEW}} | %{$_ -replace "if 0","if 1"} + shell: bash + run: | + sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}} + cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} - name: Build wolfssl library working-directory: ${{env.GITHUB_WORKSPACE}}wolfssl @@ -117,11 +116,12 @@ jobs: # These env paths already include the wolfssh/ and wolfssl/ checkout # prefixes, so they must run from the workspace root (as the build job does). - - name: updated user_settings.h for sshd and x509 - run: cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} - - - name: replace wolfSSL user_settings.h with wolfSSH user_settings.h - run: get-content ${{env.USER_SETTINGS_H_NEW}} | %{$_ -replace "if 0","if 1"} + - name: Update user_settings.h for sshd and x509 + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}} + cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} # WholeProgramOptimization=false disables /GL so the v142-independent objects # link without a cross-version code-generation requirement (C1047). diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 0d0e44f27..8ce057bf8 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -2114,6 +2114,11 @@ static int LoadPasswdList(StrList* strList, PwMapList* mapList) int count = 0; while (strList) { + if (WSTRLEN(strList->str) >= sizeof names - 1) { + fprintf(stderr, "Ignoring over-long entry\n"); + strList = strList->next; + continue; + } WSTRNCPY(names, strList->str, sizeof names - 1); passwd = WSTRCHR(names, ':'); if (passwd != NULL) { @@ -2143,6 +2148,11 @@ static int LoadKeyboardList(StrList* strList, PwMapList* mapList, int count = 0; while (strList) { + if (WSTRLEN(strList->str) >= sizeof names - 1) { + fprintf(stderr, "Ignoring over-long entry\n"); + strList = strList->next; + continue; + } WSTRNCPY(names, strList->str, sizeof names - 1); passwd = WSTRCHR(names, ':'); if (passwd != NULL) { @@ -2179,6 +2189,11 @@ static int LoadPubKeyList(StrList* strList, int format, PwMapList* mapList) buf = NULL; bufSz = 0; + if (WSTRLEN(strList->str) >= sizeof names - 1) { + fprintf(stderr, "Ignoring over-long entry\n"); + strList = strList->next; + continue; + } WSTRNCPY(names, strList->str, sizeof names - 1); fileName = WSTRCHR(names, ':'); if (fileName != NULL) { @@ -2695,6 +2710,13 @@ static INLINE void SignalTcpReady(tcp_ready* ready, word16 port) ready->port = port; pthread_cond_signal(&ready->cond); pthread_mutex_unlock(&ready->mutex); +#elif defined(USE_WINDOWS_API) && defined(NO_MAIN_DRIVER) && \ + !defined(SINGLE_THREADED) + EnterCriticalSection(&ready->cs); + ready->ready = 1; + ready->port = port; + LeaveCriticalSection(&ready->cs); + SetEvent(ready->readyEvent); #else WOLFSSH_UNUSED(ready); WOLFSSH_UNUSED(port); @@ -2840,7 +2862,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) } else { port = (word16)atoi(myoptarg); - #if !defined(NO_MAIN_DRIVER) || defined(USE_WINDOWS_API) + #if !defined(NO_MAIN_DRIVER) if (port == 0) { ES_ERROR("port number cannot be 0"); } diff --git a/examples/sftpclient/sftpclient.c b/examples/sftpclient/sftpclient.c index b7e7c332d..cdf88b230 100644 --- a/examples/sftpclient/sftpclient.c +++ b/examples/sftpclient/sftpclient.c @@ -182,6 +182,8 @@ static void myStatusCb(WOLFSSH* sshIn, word32* bytes, char* name) lastPrintedBytes[0] = 0; lastPrintedBytes[1] = 0; WMEMSET(currentFile, 0, WOLFSSH_MAX_FILENAME); + if (WSTRLEN(name) >= WOLFSSH_MAX_FILENAME) + return; WSTRNCPY(currentFile, name, WOLFSSH_MAX_FILENAME); } elapsedTime = currentTime - startTime; @@ -1462,6 +1464,8 @@ static int doAutopilot(int cmd, char* local, char* remote) if (remoteAbsPath) { /* use remote absolute path if provided */ + if (WSTRLEN(remote) >= sizeof(fullpath) - 1) + return WS_BUFFER_E; WMEMSET(fullpath, 0, sizeof(fullpath)); WSTRNCPY(fullpath, remote, sizeof(fullpath) - 1); } diff --git a/src/wolfscp.c b/src/wolfscp.c index 12ecf8ae7..220193620 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -1303,7 +1303,7 @@ static int ScpCheckForRename(WOLFSSH* ssh, int cmdSz) return WS_BUFFER_E; } - WSTRNCPY(buf, ssh->scpBasePath, cmdSz); + WSTRNCPY(buf, ssh->scpBasePath, sizeof(buf)); buf[sz] = '\0'; WSTRNCAT(buf, "/..", DEFAULT_SCP_MSG_SZ); @@ -1904,7 +1904,11 @@ static char* MakeScpCmd(const char* name, char dir, void* heap) char* cmd; int sz; +#ifdef USE_WINDOWS_API + sz = WSCPRINTF("scp -%c %s", dir, name) + 1; +#else sz = WSNPRINTF(NULL, 0, "scp -%c %s", dir, name) + 1; +#endif if (sz <= 0) { return NULL; } @@ -2587,6 +2591,9 @@ int ScpPushDir(void *fs, ScpSendCtx* ctx, const char* path, void* heap) if (ctx == NULL || path == NULL) return WS_BAD_ARGUMENT; + if (WSTRLEN(path) >= DEFAULT_SCP_FILE_NAME_SZ - 1) + return WS_BUFFER_E; + entry = ScpNewDir(fs, path, heap); if (entry == NULL) { return WS_FATAL_ERROR; diff --git a/tests/api.c b/tests/api.c index d511419ca..9f96d1933 100644 --- a/tests/api.c +++ b/tests/api.c @@ -1524,10 +1524,8 @@ static void test_wolfSSH_SFTP_SendReadPacket(void) argsCount = 0; args[argsCount++] = "."; args[argsCount++] = "-1"; -#ifndef USE_WINDOWS_API args[argsCount++] = "-p"; args[argsCount++] = "0"; -#endif ser.argv = (char**)args; ser.argc = argsCount; ser.signal = &ready; @@ -1709,8 +1707,12 @@ static void test_wolfSSH_SFTP_SendReadPacket(void) } argsCount = wolfSSH_shutdown(ssh); - if (argsCount == WS_SOCKET_ERROR_E) { - /* If the socket is closed on shutdown, peer is gone, this is OK. */ + if (argsCount == WS_SOCKET_ERROR_E || + (argsCount == WS_ERROR && + wolfSSH_get_error(ssh) == WS_SOCKET_ERROR_E)) { + /* If the socket is closed on shutdown, peer is gone, this is OK. On + * Windows the recv path returns generic WS_FATAL_ERROR with the specific + * code stashed in wolfSSH_get_error. */ argsCount = WS_SUCCESS; } @@ -1734,6 +1736,7 @@ static void test_wolfSSH_SFTP_SendReadPacket(void) k_sleep(Z_TIMEOUT_TICKS(100)); #endif ThreadJoin(serThread); + FreeTcpReady(&ready); } @@ -1794,10 +1797,8 @@ static void test_wolfSSH_SFTP_PartialSend(void) argsCount = 0; args[argsCount++] = "."; args[argsCount++] = "-1"; -#ifndef USE_WINDOWS_API args[argsCount++] = "-p"; args[argsCount++] = "0"; -#endif ser.argv = (char**)args; ser.argc = argsCount; ser.signal = &ready; @@ -1984,7 +1985,11 @@ static void test_wolfSSH_SFTP_PartialSend(void) } argsCount = wolfSSH_shutdown(ssh); - if (argsCount == WS_SOCKET_ERROR_E) { + /* Benign peer reset: WS_SOCKET_ERROR_E either as the return (send path) or in + * wolfSSH_get_error while shutdown returns generic WS_FATAL_ERROR (recv path). */ + if (argsCount == WS_SOCKET_ERROR_E || + (argsCount == WS_ERROR && + wolfSSH_get_error(ssh) == WS_SOCKET_ERROR_E)) { argsCount = WS_SUCCESS; } #if DEFAULT_HIGHWATER_MARK < 8000 @@ -2003,6 +2008,7 @@ static void test_wolfSSH_SFTP_PartialSend(void) k_sleep(Z_TIMEOUT_TICKS(100)); #endif ThreadJoin(serThread); + FreeTcpReady(&ready); #endif /* WOLFSSH_TEST_INTERNAL */ } @@ -2034,10 +2040,8 @@ static void sftp_rekey_test(int nonBlock) argsCount = 0; args[argsCount++] = "."; args[argsCount++] = "-1"; -#ifndef USE_WINDOWS_API args[argsCount++] = "-p"; args[argsCount++] = "0"; -#endif ser.argv = (char**)args; ser.argc = argsCount; ser.signal = &ready; @@ -2121,7 +2125,11 @@ static void sftp_rekey_test(int nonBlock) * before the WS_REKEYING fixup below could otherwise mask it. The loop cap * is one above the assert threshold to leave last-iteration headroom. */ AssertIntLE(tries, SFTP_REKEY_MAX_TRIES); - if (argsCount == WS_SOCKET_ERROR_E) { + /* Benign peer reset: WS_SOCKET_ERROR_E either as the return (send path) or in + * wolfSSH_get_error while shutdown returns generic WS_FATAL_ERROR (recv path). */ + if (argsCount == WS_SOCKET_ERROR_E || + (argsCount == WS_ERROR && + wolfSSH_get_error(ssh) == WS_SOCKET_ERROR_E)) { argsCount = WS_SUCCESS; } #if DEFAULT_HIGHWATER_MARK < 8000 @@ -2141,6 +2149,7 @@ static void sftp_rekey_test(int nonBlock) k_sleep(Z_TIMEOUT_TICKS(100)); #endif ThreadJoin(serThread); + FreeTcpReady(&ready); } static void test_wolfSSH_SFTP_ReKey(void) @@ -2319,10 +2328,8 @@ static void test_wolfSSH_SFTP_Confinement(void) argsCount = 0; args[argsCount++] = "."; args[argsCount++] = "-1"; -#ifndef USE_WINDOWS_API args[argsCount++] = "-p"; args[argsCount++] = "0"; -#endif ser.argv = (char**)args; ser.argc = argsCount; ser.signal = &ready; @@ -2496,7 +2503,12 @@ static void test_wolfSSH_SFTP_Confinement(void) wolfSSH_worker(ssh, NULL); ret = wolfSSH_shutdown(ssh); - if (ret == WS_SOCKET_ERROR_E) { + if (ret == WS_SOCKET_ERROR_E || + (ret == WS_ERROR && + wolfSSH_get_error(ssh) == WS_SOCKET_ERROR_E)) { + /* If the socket is closed on shutdown, peer is gone, this is OK. On + * Windows the recv path returns generic WS_FATAL_ERROR with the specific + * code stashed in wolfSSH_get_error. */ ret = WS_SUCCESS; } #if DEFAULT_HIGHWATER_MARK < 8000 @@ -2513,6 +2525,7 @@ static void test_wolfSSH_SFTP_Confinement(void) k_sleep(Z_TIMEOUT_TICKS(100)); #endif ThreadJoin(serThread); + FreeTcpReady(&ready); /* remove staged targets; escMkdir/escDest only exist if confinement * leaked, and inJailDir only if the positive-case RMDIR did not run, so @@ -2834,10 +2847,8 @@ static void scp_rekey_test(int nonBlock, int toServer) argsCount = 0; args[argsCount++] = "."; args[argsCount++] = "-1"; -#ifndef USE_WINDOWS_API args[argsCount++] = "-p"; args[argsCount++] = "0"; -#endif ser.argv = (char**)args; ser.argc = argsCount; ser.signal = &ready; @@ -2932,6 +2943,7 @@ static void scp_rekey_test(int nonBlock, int toServer) wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); ThreadJoin(serThread); + FreeTcpReady(&ready); /* verify the transferred file matches the source once the server is done */ AssertIntEQ(scpFilesMatch(verifyName, fileData, SCP_REKEY_FILE_SZ), 0); @@ -3648,10 +3660,8 @@ static void test_wolfSSH_KeyboardInteractive(void) args[argsCount++] = "-1"; args[argsCount++] = "-i"; args[argsCount++] = "test:test"; -#ifndef USE_WINDOWS_API args[argsCount++] = "-p"; args[argsCount++] = "0"; -#endif ser.argv = (char**)args; ser.argc = argsCount; ser.signal = &ready; @@ -3665,8 +3675,12 @@ static void test_wolfSSH_KeyboardInteractive(void) argsCount = wolfSSH_shutdown(ssh); - if (argsCount == WS_SOCKET_ERROR_E) { - /* If the socket is closed on shutdown, peer is gone, this is OK. */ + if (argsCount == WS_SOCKET_ERROR_E || + (argsCount == WS_ERROR && + wolfSSH_get_error(ssh) == WS_SOCKET_ERROR_E)) { + /* If the socket is closed on shutdown, peer is gone, this is OK. On + * Windows the recv path returns generic WS_FATAL_ERROR with the specific + * code stashed in wolfSSH_get_error. */ argsCount = WS_SUCCESS; } @@ -3690,6 +3704,7 @@ static void test_wolfSSH_KeyboardInteractive(void) k_sleep(Z_TIMEOUT_TICKS(100)); #endif ThreadJoin(serThread); + FreeTcpReady(&ready); } #else /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */ @@ -3708,6 +3723,8 @@ int wolfSSH_ApiTest(int argc, char** argv) #ifdef WOLFSSH_TEST_BLOCK return 77; #else + WSTARTTCP(); + AssertIntEQ(wolfSSH_Init(), WS_SUCCESS); #if defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,2) diff --git a/wolfssh/port.h b/wolfssh/port.h index 9661bf3e9..668a5b01f 100644 --- a/wolfssh/port.h +++ b/wolfssh/port.h @@ -551,6 +551,7 @@ extern "C" { #define WSTRNCASECMP(s1,s2,n) _strnicmp((s1),(s2),(n)) #define WSNPRINTF(s,n,f,...) _snprintf_s((s),(n),_TRUNCATE,(f),##__VA_ARGS__) #define WVSNPRINTF(s,n,f,...) _vsnprintf_s((s),(n),_TRUNCATE,(f),##__VA_ARGS__) + #define WSCPRINTF(f,...) _scprintf((f),##__VA_ARGS__) #define WSTRTOK(s1,s2,s3) strtok_s((s1),(s2),(s3)) #elif defined(MICROCHIP_MPLAB_HARMONY) || defined(MICROCHIP_PIC32) #include diff --git a/wolfssh/test.h b/wolfssh/test.h index 0f22fd64c..d70338f3d 100644 --- a/wolfssh/test.h +++ b/wolfssh/test.h @@ -676,7 +676,7 @@ static INLINE void tcp_listen(WS_SOCKET_T* sockfd, word16* port, int useAnyAddr) err_sys("tcp listen failed"); #endif - #if !defined(USE_WINDOWS_API) && !defined(WOLFSSL_TIRTOS) && !defined(WOLFSSL_NUCLEUS) + #if !defined(WOLFSSL_TIRTOS) && !defined(WOLFSSL_NUCLEUS) if (*port == 0) { socklen_t len = sizeof(addr); if (getsockname(*sockfd, (struct sockaddr*)&addr, &len) == 0) { @@ -826,6 +826,9 @@ typedef struct tcp_ready { #if defined(_POSIX_THREADS) && !defined(__MINGW32__) pthread_mutex_t mutex; pthread_cond_t cond; +#elif defined(USE_WINDOWS_API) + CRITICAL_SECTION cs; + HANDLE readyEvent; /* manual-reset event, set when ready */ #endif } tcp_ready; @@ -849,6 +852,14 @@ typedef struct func_args { #ifdef WOLFSSH_TEST_LOCKING +#if defined(USE_WINDOWS_API) && defined(NO_MAIN_DRIVER) && \ + !defined(SINGLE_THREADED) +/* Upper bound on the wait for the server to become ready. The signal fires + * right after the listen() so the real wait is sub-second; this generous + * bound only keeps a dead server from hanging the run. */ +#define TCP_READY_TIMEOUT_MS 30000 +#endif + static INLINE void InitTcpReady(tcp_ready* ready) { ready->ready = 0; @@ -858,6 +869,10 @@ static INLINE void InitTcpReady(tcp_ready* ready) !defined(__MINGW32__) && !defined(SINGLE_THREADED) pthread_mutex_init(&ready->mutex, NULL); pthread_cond_init(&ready->cond, NULL); +#elif defined(USE_WINDOWS_API) && defined(NO_MAIN_DRIVER) && \ + !defined(SINGLE_THREADED) + InitializeCriticalSection(&ready->cs); + ready->readyEvent = CreateEvent(NULL, TRUE, FALSE, NULL); #endif } @@ -868,6 +883,13 @@ static INLINE void FreeTcpReady(tcp_ready* ready) !defined(__MINGW32__) && !defined(SINGLE_THREADED) pthread_mutex_destroy(&ready->mutex); pthread_cond_destroy(&ready->cond); +#elif defined(USE_WINDOWS_API) && defined(NO_MAIN_DRIVER) && \ + !defined(SINGLE_THREADED) + if (ready->readyEvent != NULL) { + CloseHandle(ready->readyEvent); + ready->readyEvent = NULL; + } + DeleteCriticalSection(&ready->cs); #else WOLFSSH_UNUSED(ready); #endif @@ -891,6 +913,11 @@ static INLINE void WaitTcpReady(tcp_ready* ready) * seems to help. This is not ideal. (XXX) */ k_sleep(Z_TIMEOUT_TICKS(300)); #endif /* WOLFSSH_ZEPHYR */ +#elif defined(USE_WINDOWS_API) && defined(NO_MAIN_DRIVER) && \ + !defined(SINGLE_THREADED) + /* manual-reset event, so a late waiter still sees the ready state; on + * timeout fall through with port still 0 so the caller fails fast. */ + WaitForSingleObject(ready->readyEvent, TCP_READY_TIMEOUT_MS); #else WOLFSSH_UNUSED(ready); #endif