From b064ffc0a11724cb4abe911170f31aea8ee10fb3 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Mon, 7 Sep 2026 19:13:21 -0600 Subject: [PATCH 01/13] Add a per-instance WC_RNG lock and POSIX-conforming fork handlers --- .github/configs/os-check-linux.json | 12 + .wolfssl_known_macro_extras | 3 + CMakeLists.txt | 73 +++++ cmake/options.h.in | 4 + configure.ac | 149 +++++++++ doc/dox_comments/header_files/random.h | 42 ++- examples/configs/user_settings_template.h | 12 + tests/api/test_ossl_rand.c | 20 +- wolfcrypt/src/random.c | 279 ++++++++++++++++- wolfcrypt/src/wc_port.c | 34 +- wolfcrypt/test/test.c | 359 ++++++++++++++++++++++ wolfcrypt/test/test.h | 23 ++ wolfssl/wolfcrypt/random.h | 54 ++++ 13 files changed, 1049 insertions(+), 15 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index d3ec20c7331..283db4c2462 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -248,6 +248,12 @@ "--enable-experimental"]}, {"name": "wolfssl-extra", "minutes": 1.6, "configure": ["CPPFLAGS=-DWOLFSSL_EXTRA"]}, {"name": "coding-no", "minutes": 1.5, "configure": ["--enable-coding=no"]}, +{"name": "rng-no-getpid", "minutes": 1.5, + "comment": "Default RNG fork handlers without the pid check, so the child handler alone must make the forked child reseed in the compat RAND fork test.", + "configure": ["--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, +{"name": "rng-atfork-off-no-getpid", "minutes": 1.5, + "comment": "Neither the fork handlers nor the pid check: the compat RAND fork test must see the child repeat the parent.", + "configure": ["--disable-rng-atfork", "--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, {"name": "she-ext-cmac-cryptocb", "minutes": 1.3, "configure": ["--enable-she=extended", "--enable-cmac", "--enable-cryptocb", "--enable-cryptocbutils"]}, @@ -287,7 +293,13 @@ "configure": ["--enable-she=standard", "--enable-cmac"]}, {"name": "no-verify-oid-fpki", "minutes": 1.2, "configure": ["CPPFLAGS=-DNO_VERIFY_OID -DWOLFSSL_FPKI"]}, +{"name": "rng-atfork-off", "minutes": 1.2, + "comment": "Opt out of the RNG fork handlers that are on by default, so the lock without them is built and tested.", + "configure": ["--disable-rng-atfork"]}, {"name": "no-verify-oid", "minutes": 1.1, "configure": ["CPPFLAGS=-DNO_VERIFY_OID"]}, +{"name": "rng-lock-off", "minutes": 1.1, + "comment": "Opt out of the per-instance RNG lock, so the WC_RNG layout and generate path without it are built and tested.", + "configure": ["--disable-rng-lock"]}, {"name": "rng-seed-device", "minutes": 1.1, "comment": "Seed the RNG from a nominated device. /dev/urandom stands in for a hardware RNG so the WC_RNG_SEED_DEVICE read path is actually exercised on a runner.", "configure": ["--with-rng-seed-device=/dev/urandom"]}, diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index edac9b09603..b79d99f0177 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -608,6 +608,8 @@ RNG_CR_CONDRST RNG_SR_BUSY RTC_ALARMSUBSECONDMASK_ALL RTE_CMSIS_RTOS_RTX +RTLD_NODELETE +RTLD_NOLOAD RTOS_MODULE_NET_AVAIL RTPLATFORM SAES @@ -1320,6 +1322,7 @@ __MICROBLAZE__ __MINGW32__ __MINGW64_VERSION_MAJOR __MINGW64__ +__MSYS__ __MWERKS__ __NT__ __OS2__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b48daa5a1e..e3a2b2e35d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3765,6 +3765,15 @@ if(WOLFSSL_RNG_BANK) list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_BANK_SUPPORT") endif() +# RNG lock (share one WC_RNG between threads) +add_option("WOLFSSL_RNG_LOCK" + "Enable the lock that lets one WC_RNG be shared between threads (default: enabled where the build supports it)" + "yes" "yes;no") +# the header leaves the lock out where threads or the RNG are absent +if(NOT WOLFSSL_RNG_LOCK) + list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_NO_LOCK") +endif() + # Valgrind (for unit tests) add_option("WOLFSSL_VALGRIND" "Enable valgrind for unit tests (default: disabled)" @@ -3993,6 +4002,70 @@ if(NOT WOLFSSL_STATICMEMORY STREQUAL "no") endif() endif() +# RNG fork handlers (a forked child keeps using its WC_RNG); a build that +# cannot carry them drops them. After the static memory option: it reads it. +add_option("WOLFSSL_RNG_ATFORK" + "Enable pthread_atfork handlers so a forked child can keep using a WC_RNG (default: enabled where supported)" + "yes" "yes;no") +if(WOLFSSL_RNG_ATFORK) + set(RNG_ATFORK_NEEDS "") + if(WOLFSSL_USER_SETTINGS) + set(RNG_ATFORK_NEEDS "user_settings.h to define them") + elseif(WOLFSSL_SINGLE_THREADED OR WOLFSSL_LINUX_KM) + set(RNG_ATFORK_NEEDS "threads and no kernel module") + elseif(NOT WOLFSSL_RNG OR NOT WOLFSSL_RNG_LOCK OR NOT WOLFSSL_HASH_DRBG OR + WOLFSSL_RNG_BANK OR WOLFSSL_STATICMEMORY) + set(RNG_ATFORK_NEEDS "the RNG, its lock and the Hash DRBG, and no RNG bank or static memory") + else() + # real link tests: a toolchain whose try-compile only builds a static + # library reports every function as found + set(RNG_ATFORK_SAVED_LIBS "${CMAKE_REQUIRED_LIBRARIES}") + set(RNG_ATFORK_SAVED_TARGET "${CMAKE_TRY_COMPILE_TARGET_TYPE}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + check_function_exists("pthread_atfork" HAVE_PTHREAD_ATFORK) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) + check_c_source_compiles(" + #if defined(__linux__) || defined(__ANDROID__) || \ + defined(__CYGWIN__) || defined(__MSYS__) + #define _GNU_SOURCE 1 /* as wc_port.c does */ + #endif + #include + #include + #include + #include + #ifdef __APPLE__ + #error macOS has only named semaphores, which a forked child shares + #endif + int main(void) { + Dl_info info; + const char* name; + sem_t s; + if (sem_init(&s, 0, 1) == 0) + (void)sem_post(&s); + if (dladdr((void*)(uintptr_t)main, &info) != 0) { + name = info.dli_fname; + if (name != NULL && name[0] != 0) + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + } + return 0; + }" HAVE_DLADDR) + set(CMAKE_REQUIRED_LIBRARIES "${RNG_ATFORK_SAVED_LIBS}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE "${RNG_ATFORK_SAVED_TARGET}") + if(NOT HAVE_PTHREAD_ATFORK) + set(RNG_ATFORK_NEEDS "pthread_atfork") + elseif(NOT HAVE_DLADDR) + set(RNG_ATFORK_NEEDS "dladdr, dlopen and unnamed POSIX semaphores") + endif() + endif() + if(RNG_ATFORK_NEEDS) + message(STATUS "RNG fork handlers off: they need ${RNG_ATFORK_NEEDS}") + else() + list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_ATFORK") + list(APPEND WOLFSSL_LINK_LIBS ${CMAKE_DL_LIBS}) + endif() +endif() + # TLS (enabled by default; disable for wolfCrypt-only) add_option("WOLFSSL_TLS" "Enable TLS (default: enabled)" "yes" "yes;no") if(NOT WOLFSSL_TLS) diff --git a/cmake/options.h.in b/cmake/options.h.in index 0dba372e988..f4bd1681162 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -603,6 +603,10 @@ extern "C" { #cmakedefine FP_ECC #undef WC_RNG_BANK_SUPPORT #cmakedefine WC_RNG_BANK_SUPPORT +#undef WC_RNG_NO_LOCK +#cmakedefine WC_RNG_NO_LOCK +#undef WC_RNG_ATFORK +#cmakedefine WC_RNG_ATFORK #undef HAVE_VALGRIND #cmakedefine HAVE_VALGRIND #undef HAVE_CRL_MONITOR diff --git a/configure.ac b/configure.ac index edef8ca069d..8c3e7879d27 100644 --- a/configure.ac +++ b/configure.ac @@ -2790,6 +2790,48 @@ then fi +# RNG lock (share one WC_RNG between threads) +AC_ARG_ENABLE([rng-lock], + [AS_HELP_STRING([--enable-rng-lock],[Enable the lock that lets one WC_RNG be shared between threads (default: enabled where the build supports it)])], + [ ENABLED_RNG_LOCK=$enableval ], + [ ENABLED_RNG_LOCK=yes ] + ) + +case "$ENABLED_RNG_LOCK" in + yes|no) ;; + *) AC_MSG_ERROR([--enable-rng-lock takes yes or no]) ;; +esac +# only an explicit ask is an error: the default is yes +if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG" = "no" +then + AC_MSG_ERROR([--enable-rng-lock requires --enable-rng]) +fi +if test "$ENABLED_RNG_LOCK" = "no" +then + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_NO_LOCK" +fi + +# RNG fork handlers (a forked child keeps using its WC_RNG); decided below +AC_ARG_ENABLE([rng-atfork], + [AS_HELP_STRING([--enable-rng-atfork],[Enable pthread_atfork handlers so a forked child can keep using a WC_RNG (default: enabled where supported)])], + [ ENABLED_RNG_ATFORK=$enableval ], + [ ENABLED_RNG_ATFORK=yes ] + ) + +case "$ENABLED_RNG_ATFORK" in + yes|no) ;; + *) AC_MSG_ERROR([--enable-rng-atfork takes yes or no]) ;; +esac +if test "$ENABLED_RNG" = "no" || test "$ENABLED_RNG_LOCK" = "no" +then + if test "x$enable_rng_atfork" = "xyes" + then + AC_MSG_ERROR([--enable-rng-atfork requires --enable-rng and --enable-rng-lock]) + fi + ENABLED_RNG_ATFORK=no +fi + + # DTLS-SCTP AC_ARG_ENABLE([sctp], [AS_HELP_STRING([--enable-sctp],[Enable wolfSSL DTLS-SCTP support (default: disabled)])], @@ -13291,6 +13333,112 @@ then AM_CFLAGS="$AM_CFLAGS -DHAVE___UINT128_T=1" fi +# RNG lock and fork handlers need threads; the handlers need everything the +# WC_RNG_LOCK_ATFORK gate in random.h asks for +case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in + *-DSINGLE_THREADED*) ENABLED_RNG_LOCK_THREADS=no ;; + *) ENABLED_RNG_LOCK_THREADS=yes ;; +esac +if test "$ENABLED_SINGLETHREADED" = "yes" +then + ENABLED_RNG_LOCK_THREADS=no +fi +if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG_LOCK_THREADS" = "no" +then + AC_MSG_ERROR([--enable-rng-lock requires threads]) +fi +# a build that cannot carry the handlers drops them, unless asked explicitly +if test "$ENABLED_RNG_ATFORK" = "yes" +then + RNG_ATFORK_NEEDS="" + if test "$ENABLED_USERSETTINGS" = "yes" + then + # user_settings.h owns every define in that mode + RNG_ATFORK_NEEDS="user_settings.h to define WC_RNG_ATFORK" + fi + if test "$ENABLED_RNG_LOCK_THREADS" = "no" || \ + test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" + then + RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }threads and no kernel module" + fi + case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in + *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*|*-DWOLFSSL_CHECK_MEM_ZERO*) + ENABLED_RNG_ATFORK_FLAGS=no ;; + *) ENABLED_RNG_ATFORK_FLAGS=yes ;; + esac + if test "$ENABLED_SELFTEST" = "yes" || test "x$ENABLED_HASHDRBG" != "xyes" || \ + test "$ENABLED_ENTROPY_MEMUSE" != "no" || test "$ENABLED_RNG_BANK" = "yes" || \ + test "$ENABLED_RNG_ATFORK_FLAGS" = "no" || test "$ENABLED_WNR" = "yes" || \ + { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } + then + RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }the Hash DRBG with a heap and none of selftest, FIPS before v7, entropy-memuse, rng-bank, static memory, netRandom or memory zero checking" + fi + if test -z "$RNG_ATFORK_NEEDS" + then + saved_LIBS="$LIBS" + AC_SEARCH_LIBS([pthread_atfork], [pthread]) + LIBS="$saved_LIBS" + if test "$ac_cv_search_pthread_atfork" = "no" + then + RNG_ATFORK_NEEDS="pthread_atfork" + fi + fi + if test -z "$RNG_ATFORK_NEEDS" + then + # link the real pin: a bare symbol probe passes where the header + # hides dladdr or lacks the RTLD flags + saved_LIBS="$LIBS" + AC_SEARCH_LIBS([dladdr], [dl]) + AC_MSG_CHECKING([whether dladdr, dlopen and unnamed POSIX semaphores are available]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #if defined(__linux__) || defined(__ANDROID__) || \ + defined(__CYGWIN__) || defined(__MSYS__) + #define _GNU_SOURCE 1 /* as wc_port.c does */ + #endif + #include + #include + #include + #include + #ifdef __APPLE__ + #error "macOS has only named semaphores, which a forked child shares" + #endif + ]], [[ + Dl_info info; + const char* name; + sem_t s; + if (sem_init(&s, 0, 1) == 0) + (void)sem_post(&s); + if (dladdr((void*)(uintptr_t)main, &info) != 0) { + name = info.dli_fname; + if (name != NULL && name[0] != '\0') + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + } + ]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + RNG_ATFORK_NEEDS="dladdr, dlopen and unnamed POSIX semaphores"]) + RNG_ATFORK_DL_LIBS="$LIBS" + LIBS="$saved_LIBS" + fi + if test -n "$RNG_ATFORK_NEEDS" + then + if test "x$enable_rng_atfork" = "xyes" + then + AC_MSG_ERROR([--enable-rng-atfork requires $RNG_ATFORK_NEEDS]) + fi + AC_MSG_NOTICE([RNG fork handlers off: they need $RNG_ATFORK_NEEDS]) + ENABLED_RNG_ATFORK=no + fi +fi +if test "$ENABLED_RNG_ATFORK" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_ATFORK" + LIBS="$RNG_ATFORK_DL_LIBS" # -ldl where dladdr needed it + case "$ac_cv_search_dladdr" in + -l*) PC_LIBS_PRIVATE="$PC_LIBS_PRIVATE $ac_cv_search_dladdr" ;; + esac +fi + # Add HAVE_GETPID to AM_CFLAGS for inclusion in options.h if test "$ac_cv_func_getpid" = "yes" then @@ -14072,6 +14220,7 @@ echo " * XCHACHA: $ENABLED_XCHACHA" echo " * Hash DRBG: $ENABLED_HASHDRBG" echo " * SHA-256 Hash DRBG: $ENABLED_SHA256_DRBG" echo " * SHA-512 Hash DRBG: $ENABLED_SHA512_DRBG" +echo " * RNG fork handlers: $ENABLED_RNG_ATFORK" echo " * MmemUse Entropy:" echo " * (AKA: wolfEntropy): $ENABLED_ENTROPY_MEMUSE" echo " * PWDBASED: $ENABLED_PWDBASED" diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index b07b2a2e6c3..4b169b0b3bf 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -56,8 +56,29 @@ int wc_FreeNetRandom(void); (deterministic random bit generator) allocated (should be deallocated with wc_FreeRng). This is a blocking operation. + One WC_RNG may be shared between threads: each generate and reseed holds + the instance lock. WC_RNG_NO_LOCK (configure --disable-rng-lock) leaves + the lock out. A seed or hash crypto callback runs with the lock held, so + it must not use the RNG API. wc_InitRng*() and wc_FreeRng() do not lock; + initialize only a new or freed WC_RNG, with no other thread using it. + + POSIX lets a forked child of a threaded process only exec. Where the + build has pthread_atfork(), unnamed POSIX semaphores and the dladdr() + pin, fork handlers let the child keep using its WC_RNG: the parent holds + every lock across fork() and the child releases them and reseeds. The + child should use an instance it already has: wc_InitRng() there still + waits on a mutex the handlers do not cover. --disable-rng-atfork leaves + them out; a user_settings build defines WC_RNG_ATFORK to turn them on. + The child's reseed uses the configured allocator and seed source, which + must therefore work after fork(). wolfCrypt_Init() or the first + wc_InitRng() registers them; they can never be unregistered, + so the library pins itself against dlclose(). They cover WC_RNG locks + only, not clone(), vfork() or _Fork(). A fork() from inside a seed or + hash callback deadlocks. Builds without them, macOS among them, leave a + forked child only exec(). + \return 0 on success. - \return MEMORY_E XMALLOC failed + \return MEMORY_E XMALLOC or the fork handler registration failed \return WINCRYPT_E wc_GenerateSeed: failed to acquire context \return CRYPTGEN_E wc_GenerateSeed: failed to get random \return BAD_FUNC_ARG wc_RNG_GenerateBlock input is null or sz exceeds @@ -66,6 +87,8 @@ int wc_FreeNetRandom(void); DRBG_CONT_FAILURE \return RNG_FAILURE_E wc_RNG_GenerateBlock: Default error. rng’s status originally not ok, or set to DRBG_FAILED + \return BAD_MUTEX_E the lock that lets threads share this rng could not + be created; define WC_RNG_NO_LOCK to build without it \param rng random number generator to be initialized for use with a seed and key cipher @@ -108,6 +131,7 @@ int wc_InitRng(WC_RNG* rng); \return DRBG_CONT_FIPS_E Hash_gen returned DRBG_CONT_FAILURE \return RNG_FAILURE_E Default error. rng’s status originally not ok, or set to DRBG_FAILED + \return BAD_MUTEX_E the rng's lock could not be taken \param rng random number generator initialized with wc_InitRng \param output buffer to which the block is copied @@ -148,6 +172,7 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* b, word32 sz); \return DRBG_CONT_FIPS_E Hash_gen returned DRBG_CONT_FAILURE \return RNG_FAILURE_E Default error. rng’s status originally not ok, or set to DRBG_FAILED + \return BAD_MUTEX_E the rng's lock could not be taken \param rng: random number generator initialized with wc_InitRng \param b one byte buffer to which the block is copied @@ -182,9 +207,10 @@ int wc_RNG_GenerateByte(WC_RNG* rng, byte* b); \brief Should be called when RNG no longer needed in order to securely free drgb. Zeros and XFREEs rng-drbg. + The WC_RNG must have come from wc_InitRng*() or be zeroed. \return 0 on success - \return BAD_FUNC_ARG rng or rng->drgb null + \return BAD_FUNC_ARG rng is NULL \return RNG_FAILURE_E Failed to deallocated drbg \param rng random number generator initialized with wc_InitRng @@ -317,6 +343,8 @@ WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap); \return 0 On success \return BAD_FUNC_ARG If rng is NULL \return MEMORY_E Memory allocation failed + \return BAD_MUTEX_E the lock that lets threads share this rng could not + be created \param rng Pointer to store WC_RNG pointer \param nonce Nonce buffer (can be NULL) @@ -359,6 +387,9 @@ void wc_rng_free(WC_RNG* rng); \return 0 On success \return BAD_FUNC_ARG If rng is NULL \return RNG_FAILURE_E Initialization failed + \return BAD_MUTEX_E the lock that lets threads share this rng could not + be created + \return MEMORY_E the fork handlers could not be registered \param rng WC_RNG to initialize \param heap Heap hint (can be NULL) @@ -382,6 +413,9 @@ int wc_InitRng_ex(WC_RNG* rng, void* heap, int devId); \return 0 On success \return BAD_FUNC_ARG If rng is NULL \return RNG_FAILURE_E Initialization failed + \return BAD_MUTEX_E the lock that lets threads share this rng could not + be created + \return MEMORY_E the fork handlers could not be registered \param rng WC_RNG to initialize \param nonce Nonce buffer @@ -406,6 +440,9 @@ int wc_InitRngNonce(WC_RNG* rng, byte* nonce, word32 nonceSz); \return 0 On success \return BAD_FUNC_ARG If rng is NULL \return RNG_FAILURE_E Initialization failed + \return BAD_MUTEX_E the lock that lets threads share this rng could not + be created + \return MEMORY_E the fork handlers could not be registered \param rng WC_RNG to initialize \param nonce Nonce buffer @@ -453,6 +490,7 @@ int wc_SetSeed_Cb(wc_RngSeed_Cb cb); \return 0 On success \return BAD_FUNC_ARG If rng or seed is NULL \return RNG_FAILURE_E Reseed failed + \return BAD_MUTEX_E the rng's lock could not be taken \param rng WC_RNG to reseed \param seed Seed buffer diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index 011b51a649a..a5f2d262e84 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -436,6 +436,18 @@ extern "C" { #define CUSTOM_RAND_GENERATE_BLOCK my_rng_gen_block #endif +#if 0 /* Threaded build that never shares one WC_RNG between threads */ + #define WC_RNG_NO_LOCK +#endif +#if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ + /* configure probes for this; here it is asserted. Needs pthreads, + * a heap, the lock above (not WC_RNG_NO_LOCK) and unnamed POSIX + * semaphores (sem_init, so not macOS); the pin needs dladdr and dlopen, + * -ldl on glibc before 2.34, and keeps the library mapped, since the + * handlers cannot be removed */ + #define WC_RNG_ATFORK +#endif + /* ------------------------------------------------------------------------- */ /* Custom Standard Lib */ diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index c05805c1868..606dc5bc6ed 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -21,7 +21,9 @@ #include -#if defined(__linux__) || defined(__FreeBSD__) +/* the same guard as the fork test below */ +#if defined(OPENSSL_EXTRA) && defined(HAVE_GETPID) && !defined(__MINGW64__) && \ + !defined(__MINGW32__) #include #include #endif @@ -218,30 +220,36 @@ int test_wolfSSL_RAND_bytes(void) ExpectIntGE(pid, 0); if (pid == 0) { ssize_t n_written = 0; + int ok; /* Child process. */ close(pipefds[0]); - RAND_bytes(randbuf, sizeof(randbuf)); + ok = (RAND_bytes(randbuf, sizeof(randbuf)) == 1); n_written = write(pipefds[1], randbuf, sizeof(randbuf)); close(pipefds[1]); - exit(n_written == sizeof(randbuf) ? 0 : 1); + exit((ok && n_written == sizeof(randbuf)) ? 0 : 1); } - else { + else if (pid > 0) { /* Parent process. */ byte childrand[8] = {0}; int waitstatus = 0; + int reaped; close(pipefds[1]); ExpectIntEQ(RAND_bytes(randbuf, sizeof(randbuf)), 1); ExpectIntEQ(read(pipefds[0], childrand, sizeof(childrand)), sizeof(childrand)); - #ifdef WOLFSSL_NO_GETPID + #if defined(WOLFSSL_NO_GETPID) && !defined(WC_RNG_LOCK_ATFORK) + /* nothing reseeds the child: neither the pid check nor the handlers */ ExpectBufEQ(randbuf, childrand, sizeof(randbuf)); #else ExpectBufNE(randbuf, childrand, sizeof(randbuf)); #endif close(pipefds[0]); - waitpid(pid, &waitstatus, 0); + /* reap first, whatever happened above; then judge it */ + reaped = (waitpid(pid, &waitstatus, 0) == pid); + ExpectIntEQ(reaped && WIFEXITED(waitstatus) && + WEXITSTATUS(waitstatus) == 0, 1); } RAND_cleanup(); #endif diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index fff02bad256..1ed85bc1a7e 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -37,6 +37,10 @@ This library contains implementation for the random number generator. * WC_RNG_SEED_CB: Use custom seed callback function default: off * WC_RNG_BANK_SUPPORT: Enable RNG bank (pre-generated) default: off * random data support + * WC_RNG_NO_LOCK: Leave out the lock that lets threads default: off + * share one WC_RNG (lock on unless set) + * WC_RNG_ATFORK: pthread_atfork handlers so a forked default: on + * child can keep using its WC_RNG where found * WOLFSSL_RNG_USE_FULL_SEED: Use full-length seed for DRBG default: off * WOLFSSL_GENSEED_FORTEST: Use deterministic seed for testing default: off * WARNING: not for production use @@ -135,6 +139,9 @@ This library contains implementation for the random number generator. #include +#ifdef WC_RNG_LOCK_ATFORK + #include +#endif #ifdef WC_RNG_BANK_SUPPORT #include #endif @@ -487,6 +494,236 @@ static int UnlockDrbgState(void) #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ +#ifdef WC_RNG_LOCK_ATFORK +static WC_RNG_LOCK* rngList = NULL; /* every live lock, under rngListSem */ +static sem_t rngListSem; +static int rngAtForkSet = 0; /* handlers registered, never unregistered */ +static int rngForkLocked = 0; /* set in prepare; forks run one at a time */ +static int rngListDead = 0; /* a child that lost the registry fails closed */ + +/* sem_wait() is a cancellation point; a cancel here would strand the lock. */ +static int RngSemWait(sem_t* s) +{ + int ret = 0; + int old; + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); + while (sem_wait(s) != 0) { + if (errno != EINTR) { + ret = BAD_MUTEX_E; + break; + } + } + (void)pthread_setcancelstate(old, NULL); + return ret; +} + +/* Before fork(): the forking thread takes the registry and every lock. */ +static void RngAtForkPrepare(void) +{ + WC_RNG_LOCK* n; + rngForkLocked = (RngSemWait(&rngListSem) == 0); + if (!rngForkLocked) + return; /* the list cannot be walked safely */ + for (n = rngList; n != NULL; n = n->next) { + if (!n->broken && RngSemWait(&n->sem) != 0) + n->broken = 1; + } +} + +/* After fork() in the parent: give back what prepare took. */ +static void RngAtForkParent(void) +{ + WC_RNG_LOCK* n; + if (!rngForkLocked) + return; + for (n = rngList; n != NULL; n = n->next) { + if (!n->broken) + (void)sem_post(&n->sem); + } + (void)sem_post(&rngListSem); +} + +/* Child after fork(): stores and sem_post() only, all POSIX allows here. + * Every DRBG reseeds next; if prepare held nothing, everything fails closed. */ +static void RngAtForkChild(void) +{ + WC_RNG_LOCK* n; + for (n = rngList; n != NULL; n = n->next) { /* forward links stay whole */ + if (!rngForkLocked) { + n->broken = 1; + continue; + } + #ifndef NO_SHA256 + if (n->drbg != NULL) + ((DRBG_internal*)n->drbg)->reseedCtr = WC_RESEED_INTERVAL; + #endif + #ifdef WOLFSSL_DRBG_SHA512 + if (n->drbg512 != NULL) + ((DRBG_SHA512_internal*)n->drbg512)->reseedCtr = + WC_RESEED_INTERVAL; + #endif + if (!n->broken) + (void)sem_post(&n->sem); + } + if (rngForkLocked) + (void)sem_post(&rngListSem); + else + rngListDead = 1; +} + +/* Registers the handlers once; the pin runs outside drbgStateMutex. */ +int wc_RngAtForkInit(void) +{ + int ret = LockDrbgState(); + if (ret != 0) + return ret; + if (!rngAtForkSet) { + /* pin outside the lock: dlopen() takes the loader lock */ + (void)UnlockDrbgState(); + wc_RngPinImage((void*)(wc_ptr_t)RngAtForkPrepare); + ret = LockDrbgState(); + if (ret != 0) + return ret; + } + if (!rngAtForkSet) { + ret = (sem_init(&rngListSem, 0, 1) == 0) ? 0 : BAD_MUTEX_E; + if (ret == 0 && pthread_atfork(RngAtForkPrepare, RngAtForkParent, + RngAtForkChild) != 0) { + (void)sem_destroy(&rngListSem); + ret = MEMORY_E; + } + if (ret == 0) + rngAtForkSet = 1; + } + (void)UnlockDrbgState(); + return ret; +} + +/* Creates the lock under the registry and links the node. */ +static int RngRegister(WC_RNG_LOCK* n) +{ + int ret = wc_RngAtForkInit(); + if (ret != 0) + return ret; + if (rngListDead || RngSemWait(&rngListSem) != 0) + return BAD_MUTEX_E; + ret = (sem_init(&n->sem, 0, 1) == 0) ? 0 : BAD_MUTEX_E; + if (ret == 0) { + n->next = rngList; + n->prev = &rngList; + if (rngList != NULL) + rngList->prev = &n->next; + rngList = n; + } + (void)sem_post(&rngListSem); + return ret; +} + +/* Without the registry the node is leaked, broken and pointing at nothing. */ +static int RngUnregister(WC_RNG_LOCK* n) +{ + if (rngListDead || RngSemWait(&rngListSem) != 0) { + n->broken = 1; + n->drbg = NULL; + n->drbg512 = NULL; + WOLFSSL_MSG("RngUnregister: registry unavailable, node leaked"); + return BAD_MUTEX_E; + } + *n->prev = n->next; + if (n->next != NULL) + n->next->prev = n->prev; + (void)sem_post(&rngListSem); + return 0; +} + +/* Allocates the node and registers it. */ +static int RngLockInit(WC_RNG* rng) +{ + int ret; + WC_RNG_LOCK* n = (WC_RNG_LOCK*)XMALLOC(sizeof(*n), rng->heap, + DYNAMIC_TYPE_RNG); + if (n == NULL) + return MEMORY_E; + XMEMSET(n, 0, sizeof(*n)); + n->heap = rng->heap; +#ifndef NO_SHA256 + n->drbg = rng->drbg; +#endif +#ifdef WOLFSSL_DRBG_SHA512 + n->drbg512 = rng->drbg512; +#endif + ret = RngRegister(n); + if (ret != 0) { + XFREE(n, rng->heap, DYNAMIC_TYPE_RNG); + return ret; + } + rng->lock = n; + return 0; +} + +/* Safe on a zeroed WC_RNG that never got a lock. */ +static void RngLockFree(WC_RNG* rng) +{ + WC_RNG_LOCK* n = rng->lock; + if (n == NULL) + return; + if (RngUnregister(n) == 0) { + (void)sem_destroy(&n->sem); + XFREE(n, n->heap, DYNAMIC_TYPE_RNG); + } + rng->lock = NULL; +} + +static int RngLockEnter(WC_RNG* rng) +{ + if (rng->lock == NULL) + return 0; + if (rng->lock->broken) + return BAD_MUTEX_E; + return RngSemWait(&rng->lock->sem); +} + +static void RngLockExit(WC_RNG* rng) +{ + if (rng->lock != NULL) + (void)sem_post(&rng->lock->sem); +} +#elif defined(WC_RNG_HAVE_LOCK) +/* Without fork handlers the lock lives in the WC_RNG itself: no heap. */ +static int RngLockInit(WC_RNG* rng) +{ + if (wc_InitMutex(&rng->lock) != 0) + return BAD_MUTEX_E; + rng->lockInited = 1; + return 0; +} + +/* Safe on a zeroed WC_RNG that never got a lock. */ +static void RngLockFree(WC_RNG* rng) +{ + if (rng->lockInited) { + (void)wc_FreeMutex(&rng->lock); + rng->lockInited = 0; + } +} + +static int RngLockEnter(WC_RNG* rng) +{ + if (rng->lockInited && wc_LockMutex(&rng->lock) != 0) + return BAD_MUTEX_E; + return 0; +} + +static void RngLockExit(WC_RNG* rng) +{ + if (rng->lockInited) + (void)wc_UnLockMutex(&rng->lock); +} +#else +#define RngLockEnter(rng) 0 +#define RngLockExit(rng) WC_DO_NOTHING +#endif /* WC_RNG_HAVE_LOCK */ + static int wc_RNG_HealthTestLocal(WC_RNG* rng, int reseed, void* heap, int devId); @@ -706,6 +943,7 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) #ifndef NO_SHA256 if (rng->drbgType == WC_DRBG_SHA256) { + int ret; if (rng->drbg == NULL) { #if defined(HAVE_INTEL_RDSEED) || defined(HAVE_INTEL_RDRAND) if (IS_INTEL_RDRAND(intel_flags)) { @@ -715,12 +953,18 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) #endif return BAD_FUNC_ARG; } - return Hash_DRBG_Reseed((DRBG_internal *)rng->drbg, seed, seedSz, - NULL, 0); + ret = RngLockEnter(rng); + if (ret != 0) + return ret; + ret = Hash_DRBG_Reseed((DRBG_internal *)rng->drbg, seed, seedSz, + NULL, 0); + RngLockExit(rng); + return ret; } #endif #ifdef WOLFSSL_DRBG_SHA512 if (rng->drbgType == WC_DRBG_SHA512) { + int ret; if (rng->drbg512 == NULL) { #if defined(HAVE_INTEL_RDSEED) || defined(HAVE_INTEL_RDRAND) if (IS_INTEL_RDRAND(intel_flags)) { @@ -730,8 +974,13 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) #endif return BAD_FUNC_ARG; } - return Hash512_DRBG_Reseed((DRBG_SHA512_internal *)rng->drbg512, - seed, seedSz, NULL, 0); + ret = RngLockEnter(rng); + if (ret != 0) + return ret; + ret = Hash512_DRBG_Reseed((DRBG_SHA512_internal *)rng->drbg512, + seed, seedSz, NULL, 0); + RngLockExit(rng); + return ret; } #endif @@ -2345,6 +2594,13 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, #endif /* HAVE_HASHDRBG */ #endif /* CUSTOM_RAND_GENERATE_BLOCK */ +#ifdef WC_RNG_HAVE_LOCK + if (ret == 0) { + ret = RngLockInit(rng); + if (ret != 0) + (void)wc_FreeRng(rng); + } +#endif return ret; } @@ -2594,8 +2850,14 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) if (sz > RNG_MAX_BLOCK_LEN) return BAD_FUNC_ARG; - if (rng->status != DRBG_OK) + ret = RngLockEnter(rng); + if (ret != 0) + return ret; + + if (rng->status != DRBG_OK) { + RngLockExit(rng); return RNG_FAILURE_E; + } #if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) if (rng->pid != getpid()) { @@ -2603,6 +2865,7 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) ret = PollAndReSeed(rng); if (ret != DRBG_SUCCESS) { rng->status = DRBG_FAILED; + RngLockExit(rng); return RNG_FAILURE_E; } } @@ -2650,6 +2913,7 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) ret = RNG_FAILURE_E; rng->status = DRBG_FAILED; } + RngLockExit(rng); #else /* if we get here then there is an RNG configuration error */ @@ -2713,8 +2977,11 @@ int wc_FreeRng(WC_RNG* rng) #ifdef WC_RNG_BANK_SUPPORT if (rng->status == WC_DRBG_BANKREF) - return wc_BankRef_Release(rng); + return wc_BankRef_Release(rng); /* a bank ref never had a lock */ #endif /* WC_RNG_BANK_SUPPORT */ +#ifdef WC_RNG_HAVE_LOCK + RngLockFree(rng); +#endif #if defined(WOLFSSL_ASYNC_CRYPT) wolfAsync_DevCtxFree(&rng->asyncDev, WOLFSSL_ASYNC_MARKER_RNG); diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 4c6fbe7bdf6..b06624cbad5 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -26,6 +26,9 @@ #elif defined(__FreeBSD__) /* for __FreeBSD_version */ #include +#elif (defined(__CYGWIN__) || defined(__MSYS__)) && !defined(_GNU_SOURCE) + /* dladdr and Dl_info, for the RNG fork handler pin, hide behind it */ + #define _GNU_SOURCE 1 #endif /* @@ -115,8 +118,12 @@ Threading/Mutex options: #ifdef WOLFSSL_ASYNC_CRYPT #include #endif -#if defined(HAVE_HASHDRBG) && !defined(WC_NO_RNG) +#ifndef WC_NO_RNG + /* random.h defines HAVE_HASHDRBG itself, so no HAVE_HASHDRBG test here */ #include + #ifdef WC_RNG_LOCK_ATFORK + #include /* the pin, which the handlers require */ + #endif #endif #ifdef FREESCALE_LTC_TFM @@ -415,6 +422,24 @@ static WC_DECLARE_INIT_STATE(wolfcrypt_init_state); int aarch64_use_sb = 0; #endif +#ifdef WC_RNG_LOCK_ATFORK +#if !defined(RTLD_NOLOAD) || !defined(RTLD_NODELETE) + #error "WC_RNG_ATFORK needs RTLD_NOLOAD and RTLD_NODELETE" +#endif +/* The fork handlers can never be unregistered, so the image that holds them + * is pinned against dlclose() before they are registered. */ +WOLFSSL_LOCAL void wc_RngPinImage(void* fn) +{ + Dl_info info; + const char* name; /* a pointer on most libcs, an array on Cygwin */ + if (dladdr(fn, &info) == 0 || (name = info.dli_fname) == NULL || + dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY) == NULL) { + /* no dlopen() handle means no dlclose() can reach this image */ + WOLFSSL_MSG("RNG fork handlers: no dlopen handle, nothing to pin"); + } +} +#endif + /* Used to initialize state for wolfcrypt return 0 on success */ @@ -555,6 +580,13 @@ int wolfCrypt_Init(void) WOLFCRYPT_INIT_RAISE_BAD_STATE(); } #endif + #ifdef WC_RNG_LOCK_ATFORK + ret = wc_RngAtForkInit(); + if (ret != 0) { + WOLFSSL_MSG("RNG fork handler registration failed"); + WOLFCRYPT_INIT_RAISE_BAD_STATE(); + } + #endif #if defined(FREESCALE_LTC_TFM) || defined(FREESCALE_LTC_ECC) ret = ksdk_port_init(); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e600a7ca633..6a0feae16eb 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -179,6 +179,14 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #include "wolfcrypt/test/test.h" #endif +#ifdef WC_TEST_RNG_FORK + #include + #include + #include + #include + #include +#endif + /* printf mappings */ #ifndef WOLFSSL_LOG_PRINTF #if defined(FREESCALE_MQX) || defined(FREESCALE_KSDK_MQX) @@ -935,6 +943,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t srp_test(void); #endif #ifndef WC_NO_RNG WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void); +#ifdef WC_TEST_RNG_LOCK +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void); +#endif #ifdef WC_RNG_BANK_SUPPORT WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void); #endif @@ -2579,6 +2590,12 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_FAIL("RANDOM test failed!\n", ret); else TEST_PASS("RANDOM test passed!\n"); +#ifdef WC_TEST_RNG_LOCK + if ((ret = random_thread_test()) != 0) + TEST_FAIL("RNGTHRD test failed!\n", ret); + else + TEST_PASS("RNGTHRD test passed!\n"); +#endif #ifdef WC_RNG_BANK_SUPPORT if ((ret = random_bank_test()) != 0) TEST_FAIL("RNGBANK test failed!\n", ret); @@ -26983,6 +27000,22 @@ static wc_test_ret_t random_rng_test(void) return ret; } +/* Freeing a zeroed WC_RNG must be a no-op. Not on Versal (wc_FreeRng resets + * its TRNG) nor where the DRBG is embedded and too big for this stack. */ +#if !defined(WOLFSSL_XILINX_CRYPT_VERSAL) && \ + !(defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_STATIC_MEMORY)) +static wc_test_ret_t rng_zeroed_free_test(void) +{ + WC_RNG zeroed; + int ret; + XMEMSET(&zeroed, 0, sizeof(zeroed)); + ret = wc_FreeRng(&zeroed); + return ret == 0 ? 0 : WC_TEST_RET_ENC_EC(ret); +} +#else +#define rng_zeroed_free_test() ((wc_test_ret_t)0) +#endif + #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ !defined(HAVE_INTEL_RDRAND) @@ -27192,6 +27225,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) wc_test_ret_t ret; WOLFSSL_ENTER("random_test"); + ret = rng_zeroed_free_test(); + if (ret != 0) + return ret; + #ifndef NO_SHA256 ret = wc_RNG_HealthTest(0, test1Entropy, sizeof(test1Entropy), NULL, 0, output, sizeof(output)); @@ -27408,6 +27445,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) { WOLFSSL_ENTER("random_test"); + { + wc_test_ret_t r = rng_zeroed_free_test(); + if (r != 0) + return r; + } /* Basic RNG generate block test */ return random_rng_test(); } @@ -27838,6 +27880,323 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) #endif /* WOLFSSL_NOISE_SRC && !WC_NO_RNG */ +#ifdef WC_TEST_RNG_LOCK + +#define WC_RNG_THREAD_TEST_THREADS 4 +#define WC_RNG_THREAD_TEST_DRAWS 96 +#define WC_RNG_THREAD_TEST_BLKSZ 32 +#define WC_RNG_THREAD_TEST_BLOCKS \ + (WC_RNG_THREAD_TEST_THREADS * WC_RNG_THREAD_TEST_DRAWS) + +struct rng_thread_test_args { + WC_RNG* rng; + byte* out; /* this worker's slice, DRAWS * BLKSZ bytes */ + int reseeder; /* nonzero: this worker reseeds too */ + int ret; +}; + +/* Draws from one shared WC_RNG on several threads at once. */ +static THREAD_RETURN WOLFSSL_THREAD rng_thread_test_worker(void* arg) +{ + struct rng_thread_test_args* args = (struct rng_thread_test_args*)arg; + int i; + int ret = 0; + + for (i = 0; i < WC_RNG_THREAD_TEST_DRAWS; i++) { + ret = wc_RNG_GenerateBlock(args->rng, + args->out + + ((size_t)i * WC_RNG_THREAD_TEST_BLKSZ), + WC_RNG_THREAD_TEST_BLKSZ); + if (ret != 0) + break; + /* Two workers also reseed, against each other and the generates. */ + if (args->reseeder && ((i % 8) == 7)) { + byte seed[16]; + XMEMSET(seed, 0xa5, sizeof(seed)); + ret = wc_RNG_DRBG_Reseed(args->rng, seed, (word32)sizeof(seed)); + if (ret != 0) + break; + } + } + args->ret = ret; + WOLFSSL_RETURN_FROM_THREAD(0); +} + +#ifdef WC_TEST_RNG_FORK +#define WC_RNG_FORK_HOLD_NS 50000000L + +struct rng_fork_holder_args { + WC_RNG* rng; + int fd; /* gets one byte once the lock is held */ +}; + +/* Holds the lock while the other thread enters fork(), as a generate in + * flight would, so the prepare handler must wait for it. It works instead + * of sleeping, as a generate does: a thread checker's sleep hook needs a lock + * its fork hook holds. */ +static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_holder(void* arg) +{ + struct rng_fork_holder_args* a = (struct rng_fork_holder_args*)arg; + struct timespec start, now; + byte held = 1; + int rc = -1; + + if (a->rng->lock != NULL) { + do { + rc = sem_wait(&a->rng->lock->sem); + } while (rc != 0 && errno == EINTR); + } + if (rc == 0) { + if (write(a->fd, &held, 1) == 1 && + clock_gettime(CLOCK_MONOTONIC, &start) == 0) { + do { + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + break; + } while (now.tv_sec - start.tv_sec < 2 && /* no overflow */ + (now.tv_sec - start.tv_sec) * 1000000000L + + (now.tv_nsec - start.tv_nsec) < WC_RNG_FORK_HOLD_NS); + } + (void)sem_post(&a->rng->lock->sem); + } + close(a->fd); /* EOF if the lock was never held */ + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* fork() while another thread holds the lock: the child must finish with a + * different next block. The hold is best effort; the checks hold either way. + * Sets leak when the holder could not be joined. */ +static wc_test_ret_t rng_fork_test(WC_RNG* rng, int* leak) +{ + WC_DECLARE_VAR(parent, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + WC_DECLARE_VAR(child, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + struct rng_fork_holder_args* h = NULL; + THREAD_TYPE holder = INVALID_THREAD_VAL; /* joined only if started */ + wc_test_ret_t ret = 0; + int fd[2]; + int hfd[2]; + int piped = 0; + int started = 0; + pid_t pid = -1; + int status = 0; + byte held = 0; + + WC_ALLOC_VAR(parent, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + WC_ALLOC_VAR(child, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + h = (struct rng_fork_holder_args*)XMALLOC(sizeof(*h), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if ((! WC_VAR_OK(parent)) || (! WC_VAR_OK(child)) || (h == NULL)) + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), done); + + if (pipe(fd) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + piped = 1; + if (pipe(hfd) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + piped = 2; + + h->rng = rng; + h->fd = hfd[1]; + if (wolfSSL_NewThread(&holder, &rng_fork_test_holder, h) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + started = 1; + if (read(hfd[0], &held, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + + pid = fork(); + if (pid == 0) { + if (wc_RNG_GenerateBlock(rng, child, WC_RNG_THREAD_TEST_BLKSZ) != 0) + _exit(1); + if (write(fd[1], child, WC_RNG_THREAD_TEST_BLKSZ) != + (ssize_t)WC_RNG_THREAD_TEST_BLKSZ) + _exit(1); + /* exec so a leak checker does not blame the child for the parent's + * heap */ + execl("/bin/true", "true", (char*)NULL); + execl("/usr/bin/true", "true", (char*)NULL); + _exit(0); + } + close(fd[1]); + fd[1] = -1; + if (pid < 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + + if (read(fd[0], child, WC_RNG_THREAD_TEST_BLKSZ) != + (ssize_t)WC_RNG_THREAD_TEST_BLKSZ) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + if (waitpid(pid, &status, 0) != pid) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + pid = -1; + if ((! WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + + ret = wc_RNG_GenerateBlock(rng, parent, WC_RNG_THREAD_TEST_BLKSZ); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), done); + /* Both draws follow the fork, so a match means the child kept the + * parent's state; with HAVE_GETPID the pid check reseeds it too. */ + if (XMEMCMP(parent, child, WC_RNG_THREAD_TEST_BLKSZ) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + +done: + if (pid > 0) { + (void)kill(pid, SIGKILL); + (void)waitpid(pid, NULL, 0); + } + if (started && (wolfSSL_JoinThread(holder) != 0)) { + *leak = 1; /* the holder still uses h, rng and its pipe */ + h = NULL; + if (ret == 0) + ret = WC_TEST_RET_ENC_NC; + } + if (piped >= 1) { + if (fd[0] >= 0) + close(fd[0]); + if (fd[1] >= 0) + close(fd[1]); + } + if (piped >= 2) { + if (h != NULL || !started) + close(hfd[0]); /* kept open for a lost holder: no SIGPIPE */ + if (!started) + close(hfd[1]); + } + XFREE(h, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR(parent, HEAP_HINT); + WC_FREE_VAR(child, HEAP_HINT); + return ret; +} +#endif /* WC_TEST_RNG_FORK */ + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) +{ + WC_RNG* rng = NULL; + THREAD_TYPE threads[WC_RNG_THREAD_TEST_THREADS]; + struct rng_thread_test_args* args = NULL; + byte* out = NULL; + int leak = 0; /* rng, args or out may still be in use by a thread */ + int started = 0; + int nblocks; + int i, j; + wc_test_ret_t ret = 0; + + WOLFSSL_ENTER("random_thread_test"); + + /* Everything a thread can reach is on the heap, so a thread that cannot + * be joined is leaked instead of left running over a dead frame. */ + out = (byte*)XMALLOC((size_t)WC_RNG_THREAD_TEST_BLOCKS * + WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + args = (struct rng_thread_test_args*)XMALLOC( + sizeof(*args) * WC_RNG_THREAD_TEST_THREADS, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + /* INVALID_DEVID: a crypto callback would answer before the lock. */ + (void)wc_rng_new_ex(&rng, NULL, 0, HEAP_HINT, INVALID_DEVID); + if (out == NULL || args == NULL || rng == NULL) + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); + +#ifdef WC_TEST_RNG_FORK + { + /* Three registered, the middle one freed, then a fork for each + * survivor. */ + WC_RNG* mid = NULL; + WC_RNG* third = NULL; + int leak3 = 0; /* third may still be in use by its holder */ + (void)wc_rng_new_ex(&mid, NULL, 0, HEAP_HINT, INVALID_DEVID); + (void)wc_rng_new_ex(&third, NULL, 0, HEAP_HINT, INVALID_DEVID); + if (mid == NULL || third == NULL) { + if (mid != NULL) + wc_rng_free(mid); + if (third != NULL) + wc_rng_free(third); + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); + } + wc_rng_free(mid); /* the middle of three leaves the registry */ + alarm(30); /* a hung child or holder fails the run instead */ + ret = rng_fork_test(rng, &leak); + if (ret == 0) + ret = rng_fork_test(third, &leak3); + alarm(0); + if (!leak3) + wc_rng_free(third); + if (ret != 0) + goto out_free; + } + + /* A lock marked broken fails closed. */ + { + byte seed[16]; + XMEMSET(seed, 0xa5, sizeof(seed)); + rng->lock->broken = 1; + ret = wc_RNG_GenerateBlock(rng, out, WC_RNG_THREAD_TEST_BLKSZ); + if (ret != WC_NO_ERR_TRACE(BAD_MUTEX_E)) { + rng->lock->broken = 0; + ERROR_OUT(ret == 0 ? WC_TEST_RET_ENC_NC : WC_TEST_RET_ENC_EC(ret), + out_free); + } + ret = wc_RNG_DRBG_Reseed(rng, seed, (word32)sizeof(seed)); + rng->lock->broken = 0; + if (ret != WC_NO_ERR_TRACE(BAD_MUTEX_E)) { + ERROR_OUT(ret == 0 ? WC_TEST_RET_ENC_NC : WC_TEST_RET_ENC_EC(ret), + out_free); + } + ret = 0; + } +#endif + + for (i = 0; i < WC_RNG_THREAD_TEST_THREADS; i++) { + args[i].rng = rng; + args[i].out = out + ((size_t)i * WC_RNG_THREAD_TEST_DRAWS * + WC_RNG_THREAD_TEST_BLKSZ); + args[i].reseeder = (i < 2); + args[i].ret = 0; + if (wolfSSL_NewThread(&threads[i], &rng_thread_test_worker, + &args[i]) != 0) { + break; + } + started++; + } + + for (i = 0; i < started; i++) { + if (wolfSSL_JoinThread(threads[i]) != 0) + leak = 1; + } + if (leak) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); + + /* Worker errors first, whatever the thread count. */ + for (i = 0; i < started; i++) { + if (args[i].ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(args[i].ret), out_free); + } + + /* Fewer than two threads tested nothing. */ + if (started < 2) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); + + /* All pairs, as a smoke test. */ + nblocks = started * WC_RNG_THREAD_TEST_DRAWS; + for (i = 1; i < nblocks; i++) { + for (j = 0; j < i; j++) { + if (XMEMCMP(out + ((size_t)i * WC_RNG_THREAD_TEST_BLKSZ), + out + ((size_t)j * WC_RNG_THREAD_TEST_BLKSZ), + WC_RNG_THREAD_TEST_BLKSZ) == 0) { + ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); + } + } + } + +out_free: + if (leak) + return ret; /* a thread may still use rng, args or out */ + if (rng != NULL) + wc_rng_free(rng); + XFREE(args, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +#endif /* WC_TEST_RNG_LOCK */ + #ifdef WC_RNG_BANK_SUPPORT static char *rng_bank_affinity_lock_lock; diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index 0f83600be61..9416cc0b86b 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -38,6 +38,26 @@ #include #include +#ifndef WC_NO_RNG + /* for WC_RNG_HAVE_LOCK and WC_RNG_LOCK_ATFORK */ + #include +#endif + +/* Needs the lock, threads it can start, and a heap for the compare buffer. */ +#if defined(WC_RNG_HAVE_LOCK) && !defined(WOLFSSL_ASYNC_CRYPT) && \ + !defined(HAVE_INTEL_RDRAND) && !defined(WOLF_CRYPTO_CB_FIND) && \ + !(defined(WOLFSSL_SILABS_SE_ACCEL) && defined(WOLFSSL_SILABS_TRNG)) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_XILINX_CRYPT_VERSAL) && \ + (defined(WOLFSSL_PTHREADS) || \ + (defined(USE_WINDOWS_API) && !defined(_WIN32_WCE))) + #define WC_TEST_RNG_LOCK +#endif +/* The fork test needs a real process model on top of the handlers. */ +#if defined(WC_TEST_RNG_LOCK) && defined(WC_RNG_LOCK_ATFORK) && \ + (defined(__unix__) || defined(__linux__)) + #define WC_TEST_RNG_FORK +#endif #ifdef HAVE_STACK_SIZE THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args); @@ -252,6 +272,9 @@ extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t srp_test(void); #endif #ifndef WC_NO_RNG extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void); +#ifdef WC_TEST_RNG_LOCK +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void); +#endif #ifdef WC_RNG_BANK_SUPPORT extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void); #endif diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 0b416da560d..64fc9f5e4b3 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -34,6 +34,32 @@ #include #endif /* HAVE_FIPS_VERSION >= 2 */ +/* One lock per WC_RNG so threads can share it. WC_RNG_NO_LOCK opts out; + * kernel modules have their own lock-free design. */ +#if !defined(WC_RNG_NO_LOCK) && !defined(SINGLE_THREADED) && \ + !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_BSDKM) && \ + defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + #define WC_RNG_HAVE_LOCK +#endif + +/* pthread_atfork handlers so a forked child can keep using its WC_RNG. + * configure and CMake define WC_RNG_ATFORK where the dlclose pin and unnamed + * semaphores exist; builds whose locks the handlers cannot cover are left + * out. */ +#if defined(WC_RNG_HAVE_LOCK) && defined(WOLFSSL_PTHREADS) && \ + defined(WC_RNG_ATFORK) && !defined(__APPLE__) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(HAVE_ENTROPY_MEMUSE) && \ + !defined(WC_RNG_BANK_SUPPORT) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(HAVE_WNR) && !defined(WOLFSSL_CHECK_MEM_ZERO) + #define WC_RNG_LOCK_ATFORK +#endif + +#ifdef WC_RNG_LOCK_ATFORK + #include /* outside extern "C" */ +#endif + #ifdef __cplusplus extern "C" { #endif @@ -77,6 +103,23 @@ #endif #endif +#ifdef WC_RNG_LOCK_ATFORK +/* On the heap so a memset of the WC_RNG cannot break the fork registry. An + * unnamed semaphore: fork() copies it and sem_post() is the one unlock a + * child handler may call. macOS has only named ones, so no handlers there. */ +typedef struct WC_RNG_LOCK { + sem_t sem; + void* heap; + struct WC_RNG_LOCK* next; + struct WC_RNG_LOCK** prev; /* the link that leads here */ + void* drbg; /* states a fork child must reseed */ + void* drbg512; + int broken; /* fails closed after a fork went wrong */ +} WC_RNG_LOCK; +WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ +WOLFSSL_LOCAL void wc_RngPinImage(void* fn); /* keeps fn's image mapped */ +#endif + /* avoid redefinition of structs */ #if !defined(HAVE_FIPS) || \ @@ -426,6 +469,15 @@ struct WC_RNG { #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) int devId; #endif +#ifdef WC_RNG_LOCK_ATFORK + /* NULL until wc_InitRng succeeds. Initialize only a new or freed WC_RNG: + * wc_InitRng over a live one leaks this and grows the fork registry that + * every fork() walks. */ + WC_RNG_LOCK* lock; +#elif defined(WC_RNG_HAVE_LOCK) + wolfSSL_Mutex lock; /* serializes generate and reseed */ + byte lockInited; /* nonzero once lock exists */ +#endif }; #endif /* NO FIPS or have FIPS v2*/ @@ -548,6 +600,8 @@ WOLFSSL_ABI WOLFSSL_API WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap); WOLFSSL_API int wc_rng_new_ex(WC_RNG **rng, byte* nonce, word32 nonceSz, void* heap, int devId); +/* wc_rng_new*, wc_InitRng*, wc_FreeRng and wc_rng_free do not take the + * instance lock: no other thread may use the instance across them. */ WOLFSSL_ABI WOLFSSL_API void wc_rng_free(WC_RNG* rng); From d965be5f0fd5aed8b8af86930dcd13ecedcb8b85 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 14:32:00 -0600 Subject: [PATCH 02/13] Tighten the fork handler edges skoll flagged and add a single-threaded CI build --- .github/configs/os-check-linux.json | 3 +++ configure.ac | 7 ++++++- doc/dox_comments/header_files/random.h | 3 ++- examples/configs/user_settings_template.h | 3 ++- tests/api/test_ossl_rand.c | 4 ++++ wolfcrypt/src/random.c | 5 +++-- wolfcrypt/src/wc_port.c | 1 + wolfcrypt/test/test.h | 10 +++++----- wolfssl/wolfcrypt/random.h | 5 +++-- 9 files changed, 29 insertions(+), 12 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 283db4c2462..38ad5b25a4a 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -293,6 +293,9 @@ "configure": ["--enable-she=standard", "--enable-cmac"]}, {"name": "no-verify-oid-fpki", "minutes": 1.2, "configure": ["CPPFLAGS=-DNO_VERIFY_OID -DWOLFSSL_FPKI"]}, +{"name": "rng-single-threaded", "minutes": 1.2, + "comment": "SINGLE_THREADED: no RNG lock and no fork handlers may be built, and the compat RAND test still passes.", + "configure": ["--enable-singlethreaded", "--enable-opensslextra"]}, {"name": "rng-atfork-off", "minutes": 1.2, "comment": "Opt out of the RNG fork handlers that are on by default, so the lock without them is built and tested.", "configure": ["--disable-rng-atfork"]}, diff --git a/configure.ac b/configure.ac index 8c3e7879d27..27a2a1c36ec 100644 --- a/configure.ac +++ b/configure.ac @@ -13347,6 +13347,10 @@ if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG_LOCK_THREADS" = "no" then AC_MSG_ERROR([--enable-rng-lock requires threads]) fi +if test "$ENABLED_RNG_LOCK_THREADS" = "no" +then + ENABLED_RNG_LOCK=no +fi # a build that cannot carry the handlers drops them, unless asked explicitly if test "$ENABLED_RNG_ATFORK" = "yes" then @@ -13362,7 +13366,7 @@ then RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }threads and no kernel module" fi case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in - *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*|*-DWOLFSSL_CHECK_MEM_ZERO*) + *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*|*-DWOLFSSL_CHECK_MEM_ZERO*|*-DWOLFSSL_USER_MUTEX*) ENABLED_RNG_ATFORK_FLAGS=no ;; *) ENABLED_RNG_ATFORK_FLAGS=yes ;; esac @@ -14220,6 +14224,7 @@ echo " * XCHACHA: $ENABLED_XCHACHA" echo " * Hash DRBG: $ENABLED_HASHDRBG" echo " * SHA-256 Hash DRBG: $ENABLED_SHA256_DRBG" echo " * SHA-512 Hash DRBG: $ENABLED_SHA512_DRBG" +echo " * RNG lock: $ENABLED_RNG_LOCK" echo " * RNG fork handlers: $ENABLED_RNG_ATFORK" echo " * MmemUse Entropy:" echo " * (AKA: wolfEntropy): $ENABLED_ENTROPY_MEMUSE" diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index 4b169b0b3bf..f1bfb6fdbc2 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -65,7 +65,8 @@ int wc_FreeNetRandom(void); POSIX lets a forked child of a threaded process only exec. Where the build has pthread_atfork(), unnamed POSIX semaphores and the dladdr() pin, fork handlers let the child keep using its WC_RNG: the parent holds - every lock across fork() and the child releases them and reseeds. The + every lock across fork() and the child releases them and reseeds, so + each fork() waits for every live instance's generate in flight. The child should use an instance it already has: wc_InitRng() there still waits on a mutex the handlers do not cover. --disable-rng-atfork leaves them out; a user_settings build defines WC_RNG_ATFORK to turn them on. diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index a5f2d262e84..ca8714aa81c 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -444,7 +444,8 @@ extern "C" { * a heap, the lock above (not WC_RNG_NO_LOCK) and unnamed POSIX * semaphores (sem_init, so not macOS); the pin needs dladdr and dlopen, * -ldl on glibc before 2.34, and keeps the library mapped, since the - * handlers cannot be removed */ + * handlers cannot be removed. Left out with entropy-memuse, the RNG + * bank, netRandom, selftest, FIPS before v7 or memory zero checking */ #define WC_RNG_ATFORK #endif diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index 606dc5bc6ed..e82e34a8502 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -251,6 +251,10 @@ int test_wolfSSL_RAND_bytes(void) ExpectIntEQ(reaped && WIFEXITED(waitstatus) && WEXITSTATUS(waitstatus) == 0, 1); } + else { + close(pipefds[0]); + close(pipefds[1]); + } RAND_cleanup(); #endif #endif diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 1ed85bc1a7e..f0d21f9b250 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -505,7 +505,7 @@ static int rngListDead = 0; /* a child that lost the registry fails closed */ static int RngSemWait(sem_t* s) { int ret = 0; - int old; + int old = PTHREAD_CANCEL_ENABLE; (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); while (sem_wait(s) != 0) { if (errno != EINTR) { @@ -578,7 +578,8 @@ int wc_RngAtForkInit(void) if (ret != 0) return ret; if (!rngAtForkSet) { - /* pin outside the lock: dlopen() takes the loader lock */ + /* pin outside the lock: dlopen() takes the loader lock. Racing + * first callers may both pin, which is harmless. */ (void)UnlockDrbgState(); wc_RngPinImage((void*)(wc_ptr_t)RngAtForkPrepare); ret = LockDrbgState(); diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index b06624cbad5..27c5ddd7cb4 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -433,6 +433,7 @@ WOLFSSL_LOCAL void wc_RngPinImage(void* fn) Dl_info info; const char* name; /* a pointer on most libcs, an array on Cygwin */ if (dladdr(fn, &info) == 0 || (name = info.dli_fname) == NULL || + name[0] == '\0' || dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY) == NULL) { /* no dlopen() handle means no dlclose() can reach this image */ WOLFSSL_MSG("RNG fork handlers: no dlopen handle, nothing to pin"); diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index 9416cc0b86b..284415ee86b 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -24,6 +24,10 @@ #define WOLFCRYPT_TEST_H #include +#ifndef WC_NO_RNG + /* for WC_RNG_HAVE_LOCK and WC_RNG_LOCK_ATFORK; above extern "C" */ + #include +#endif #ifdef __cplusplus extern "C" { @@ -38,10 +42,6 @@ #include #include -#ifndef WC_NO_RNG - /* for WC_RNG_HAVE_LOCK and WC_RNG_LOCK_ATFORK */ - #include -#endif /* Needs the lock, threads it can start, and a heap for the compare buffer. */ #if defined(WC_RNG_HAVE_LOCK) && !defined(WOLFSSL_ASYNC_CRYPT) && \ @@ -55,7 +55,7 @@ #endif /* The fork test needs a real process model on top of the handlers. */ #if defined(WC_TEST_RNG_LOCK) && defined(WC_RNG_LOCK_ATFORK) && \ - (defined(__unix__) || defined(__linux__)) + !defined(__STRICT_ANSI__) && (defined(__unix__) || defined(__linux__)) #define WC_TEST_RNG_FORK #endif diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 64fc9f5e4b3..58f8fe4cb5c 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -35,7 +35,8 @@ #endif /* HAVE_FIPS_VERSION >= 2 */ /* One lock per WC_RNG so threads can share it. WC_RNG_NO_LOCK opts out; - * kernel modules have their own lock-free design. */ + * kernel modules have their own lock-free design. Bank builds keep it: they + * still hand out plain instances. */ #if !defined(WC_RNG_NO_LOCK) && !defined(SINGLE_THREADED) && \ !defined(WC_NO_RNG) && \ !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_BSDKM) && \ @@ -114,7 +115,7 @@ typedef struct WC_RNG_LOCK { struct WC_RNG_LOCK** prev; /* the link that leads here */ void* drbg; /* states a fork child must reseed */ void* drbg512; - int broken; /* fails closed after a fork went wrong */ + int broken; /* fails closed; set only by a lone child or on a dead sem */ } WC_RNG_LOCK; WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ WOLFSSL_LOCAL void wc_RngPinImage(void* fn); /* keeps fn's image mapped */ From 11a928c2d74a0057b6d3e0acf1c0ca8b0b456f2d Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 15:14:48 -0600 Subject: [PATCH 03/13] Keep cancellation off while a WC_RNG lock is held and tidy the probes --- CMakeLists.txt | 8 ++++---- configure.ac | 13 ++++++------- wolfcrypt/src/random.c | 20 +++++++++++++++++--- wolfcrypt/test/test.c | 2 ++ wolfssl/wolfcrypt/random.h | 9 ++++++--- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3a2b2e35d5..ec95cab3a93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4023,7 +4023,7 @@ if(WOLFSSL_RNG_ATFORK) set(RNG_ATFORK_SAVED_TARGET "${CMAKE_TRY_COMPILE_TARGET_TYPE}") set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) - check_function_exists("pthread_atfork" HAVE_PTHREAD_ATFORK) + check_function_exists("pthread_atfork" WOLFSSL_HAVE_PTHREAD_ATFORK) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) check_c_source_compiles(" #if defined(__linux__) || defined(__ANDROID__) || \ @@ -4049,12 +4049,12 @@ if(WOLFSSL_RNG_ATFORK) (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); } return 0; - }" HAVE_DLADDR) + }" WOLFSSL_HAVE_DLADDR_PIN) set(CMAKE_REQUIRED_LIBRARIES "${RNG_ATFORK_SAVED_LIBS}") set(CMAKE_TRY_COMPILE_TARGET_TYPE "${RNG_ATFORK_SAVED_TARGET}") - if(NOT HAVE_PTHREAD_ATFORK) + if(NOT WOLFSSL_HAVE_PTHREAD_ATFORK) set(RNG_ATFORK_NEEDS "pthread_atfork") - elseif(NOT HAVE_DLADDR) + elseif(NOT WOLFSSL_HAVE_DLADDR_PIN) set(RNG_ATFORK_NEEDS "dladdr, dlopen and unnamed POSIX semaphores") endif() endif() diff --git a/configure.ac b/configure.ac index 27a2a1c36ec..3ea3f471d54 100644 --- a/configure.ac +++ b/configure.ac @@ -13347,7 +13347,7 @@ if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG_LOCK_THREADS" = "no" then AC_MSG_ERROR([--enable-rng-lock requires threads]) fi -if test "$ENABLED_RNG_LOCK_THREADS" = "no" +if test "$ENABLED_RNG_LOCK_THREADS" = "no" || test "$ENABLED_RNG" = "no" then ENABLED_RNG_LOCK=no fi @@ -13366,7 +13366,7 @@ then RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }threads and no kernel module" fi case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in - *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*|*-DWOLFSSL_CHECK_MEM_ZERO*|*-DWOLFSSL_USER_MUTEX*) + *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*|*-DWOLFSSL_CHECK_MEM_ZERO*|*-DWOLFSSL_TRACK_MEMORY*|*-DWOLFSSL_MEM_FAIL_COUNT*|*-DWOLFSSL_USER_MUTEX*) ENABLED_RNG_ATFORK_FLAGS=no ;; *) ENABLED_RNG_ATFORK_FLAGS=yes ;; esac @@ -13375,7 +13375,7 @@ then test "$ENABLED_RNG_ATFORK_FLAGS" = "no" || test "$ENABLED_WNR" = "yes" || \ { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } then - RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }the Hash DRBG with a heap and none of selftest, FIPS before v7, entropy-memuse, rng-bank, static memory, netRandom or memory zero checking" + RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }the Hash DRBG with a heap and none of selftest, FIPS before v7, entropy-memuse, rng-bank, static memory, netRandom, memory zero checking or memory tracking" fi if test -z "$RNG_ATFORK_NEEDS" then @@ -13421,7 +13421,6 @@ then [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) RNG_ATFORK_NEEDS="dladdr, dlopen and unnamed POSIX semaphores"]) - RNG_ATFORK_DL_LIBS="$LIBS" LIBS="$saved_LIBS" fi if test -n "$RNG_ATFORK_NEEDS" @@ -13437,9 +13436,9 @@ fi if test "$ENABLED_RNG_ATFORK" = "yes" then AM_CFLAGS="$AM_CFLAGS -DWC_RNG_ATFORK" - LIBS="$RNG_ATFORK_DL_LIBS" # -ldl where dladdr needed it - case "$ac_cv_search_dladdr" in - -l*) PC_LIBS_PRIVATE="$PC_LIBS_PRIVATE $ac_cv_search_dladdr" ;; + case "$ac_cv_search_dladdr" in # -ldl where dladdr needed it + -l*) LIBS="$ac_cv_search_dladdr $LIBS" + PC_LIBS_PRIVATE="$PC_LIBS_PRIVATE $ac_cv_search_dladdr" ;; esac fi diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index f0d21f9b250..945bd9f2619 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -675,19 +675,33 @@ static void RngLockFree(WC_RNG* rng) rng->lock = NULL; } +/* Cancellation stays off while the lock is held: a reseed reads a device, + * a cancellation point, and a cancelled holder would strand every fork(). */ static int RngLockEnter(WC_RNG* rng) { + int old = PTHREAD_CANCEL_ENABLE; + int ret; if (rng->lock == NULL) return 0; if (rng->lock->broken) return BAD_MUTEX_E; - return RngSemWait(&rng->lock->sem); + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); + ret = RngSemWait(&rng->lock->sem); + if (ret != 0) + (void)pthread_setcancelstate(old, NULL); + else + rng->lock->cancel = old; + return ret; } static void RngLockExit(WC_RNG* rng) { - if (rng->lock != NULL) - (void)sem_post(&rng->lock->sem); + int old; + if (rng->lock == NULL) + return; + old = rng->lock->cancel; + (void)sem_post(&rng->lock->sem); + (void)pthread_setcancelstate(old, NULL); } #elif defined(WC_RNG_HAVE_LOCK) /* Without fork handlers the lock lives in the WC_RNG itself: no heap. */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 6a0feae16eb..0000c6584bf 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28168,6 +28168,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) if (args[i].ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(args[i].ret), out_free); } + if (rng->status != WC_DRBG_OK) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); /* Fewer than two threads tested nothing. */ if (started < 2) diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 58f8fe4cb5c..0d01777d574 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -53,7 +53,8 @@ defined(WC_RNG_ATFORK) && !defined(__APPLE__) && \ !defined(WOLFSSL_NO_MALLOC) && !defined(HAVE_ENTROPY_MEMUSE) && \ !defined(WC_RNG_BANK_SUPPORT) && !defined(WOLFSSL_STATIC_MEMORY) && \ - !defined(HAVE_WNR) && !defined(WOLFSSL_CHECK_MEM_ZERO) + !defined(HAVE_WNR) && !defined(WOLFSSL_CHECK_MEM_ZERO) && \ + !defined(WOLFSSL_TRACK_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) #define WC_RNG_LOCK_ATFORK #endif @@ -116,6 +117,7 @@ typedef struct WC_RNG_LOCK { void* drbg; /* states a fork child must reseed */ void* drbg512; int broken; /* fails closed; set only by a lone child or on a dead sem */ + int cancel; /* the holder's cancel state, back on exit */ } WC_RNG_LOCK; WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ WOLFSSL_LOCAL void wc_RngPinImage(void* fn); /* keeps fn's image mapped */ @@ -601,8 +603,9 @@ WOLFSSL_ABI WOLFSSL_API WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap); WOLFSSL_API int wc_rng_new_ex(WC_RNG **rng, byte* nonce, word32 nonceSz, void* heap, int devId); -/* wc_rng_new*, wc_InitRng*, wc_FreeRng and wc_rng_free do not take the - * instance lock: no other thread may use the instance across them. */ +/* wc_rng_new*, wc_InitRng*, wc_InitRng_BankRef, wc_FreeRng and wc_rng_free + * do not take the instance lock: no other thread may use the instance across + * them. */ WOLFSSL_ABI WOLFSSL_API void wc_rng_free(WC_RNG* rng); From 3738cdbc9dbe988a62a4d5d87fcff3f747a7e354 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 15:51:14 -0600 Subject: [PATCH 04/13] Define HAVE_HASHDRBG before the lock gates and fail closed on a dead registry --- cmake/options.h.in | 2 ++ configure.ac | 8 +++++++- wolfcrypt/src/random.c | 20 ++++++++++---------- wolfcrypt/test/test.c | 4 +++- wolfssl/wolfcrypt/random.h | 20 ++++++++++---------- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/cmake/options.h.in b/cmake/options.h.in index f4bd1681162..e61eba58ec0 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -607,6 +607,8 @@ extern "C" { #cmakedefine WC_RNG_NO_LOCK #undef WC_RNG_ATFORK #cmakedefine WC_RNG_ATFORK +#undef HAVE_GETPID +#cmakedefine HAVE_GETPID #undef HAVE_VALGRIND #cmakedefine HAVE_VALGRIND #undef HAVE_CRL_MONITOR diff --git a/configure.ac b/configure.ac index 3ea3f471d54..e6bd600ab8c 100644 --- a/configure.ac +++ b/configure.ac @@ -13347,7 +13347,13 @@ if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG_LOCK_THREADS" = "no" then AC_MSG_ERROR([--enable-rng-lock requires threads]) fi -if test "$ENABLED_RNG_LOCK_THREADS" = "no" || test "$ENABLED_RNG" = "no" +case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in + *-DCUSTOM_RAND_GENERATE_BLOCK*) ENABLED_RNG_LOCK=no ;; +esac +if test "$ENABLED_RNG_LOCK_THREADS" = "no" || test "$ENABLED_RNG" = "no" || \ + test "$ENABLED_SELFTEST" = "yes" || test "x$ENABLED_HASHDRBG" != "xyes" || \ + test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" || \ + { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } then ENABLED_RNG_LOCK=no fi diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 945bd9f2619..d38bb96b12e 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -498,8 +498,7 @@ static int UnlockDrbgState(void) static WC_RNG_LOCK* rngList = NULL; /* every live lock, under rngListSem */ static sem_t rngListSem; static int rngAtForkSet = 0; /* handlers registered, never unregistered */ -static int rngForkLocked = 0; /* set in prepare; forks run one at a time */ -static int rngListDead = 0; /* a child that lost the registry fails closed */ +static int rngListDead = 0; /* registry unusable: every handler backs off */ /* sem_wait() is a cancellation point; a cancel here would strand the lock. */ static int RngSemWait(sem_t* s) @@ -521,9 +520,12 @@ static int RngSemWait(sem_t* s) static void RngAtForkPrepare(void) { WC_RNG_LOCK* n; - rngForkLocked = (RngSemWait(&rngListSem) == 0); - if (!rngForkLocked) - return; /* the list cannot be walked safely */ + if (rngListDead) + return; + if (RngSemWait(&rngListSem) != 0) { + rngListDead = 1; /* nothing held, and never again */ + return; + } for (n = rngList; n != NULL; n = n->next) { if (!n->broken && RngSemWait(&n->sem) != 0) n->broken = 1; @@ -534,7 +536,7 @@ static void RngAtForkPrepare(void) static void RngAtForkParent(void) { WC_RNG_LOCK* n; - if (!rngForkLocked) + if (rngListDead) return; for (n = rngList; n != NULL; n = n->next) { if (!n->broken) @@ -549,7 +551,7 @@ static void RngAtForkChild(void) { WC_RNG_LOCK* n; for (n = rngList; n != NULL; n = n->next) { /* forward links stay whole */ - if (!rngForkLocked) { + if (rngListDead) { n->broken = 1; continue; } @@ -565,10 +567,8 @@ static void RngAtForkChild(void) if (!n->broken) (void)sem_post(&n->sem); } - if (rngForkLocked) + if (!rngListDead) (void)sem_post(&rngListSem); - else - rngListDead = 1; } /* Registers the handlers once; the pin runs outside drbgStateMutex. */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 0000c6584bf..e432d7761a5 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28121,7 +28121,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) if (ret != 0) goto out_free; } +#endif /* WC_TEST_RNG_FORK */ +#ifdef WC_RNG_LOCK_ATFORK /* A lock marked broken fails closed. */ { byte seed[16]; @@ -28141,7 +28143,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) } ret = 0; } -#endif +#endif /* WC_RNG_LOCK_ATFORK */ for (i = 0; i < WC_RNG_THREAD_TEST_THREADS; i++) { args[i].rng = rng; diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 0d01777d574..2c20cdb84ed 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -34,6 +34,16 @@ #include #endif /* HAVE_FIPS_VERSION >= 2 */ +/* make sure Hash DRBG is enabled, unless WC_NO_HASHDRBG is defined + or CUSTOM_RAND_GENERATE_BLOCK is defined */ +#if !defined(WC_NO_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) + #undef HAVE_HASHDRBG + #define HAVE_HASHDRBG + #ifndef WC_RESEED_INTERVAL + #define WC_RESEED_INTERVAL (1000000) + #endif +#endif + /* One lock per WC_RNG so threads can share it. WC_RNG_NO_LOCK opts out; * kernel modules have their own lock-free design. Bank builds keep it: they * still hand out plain instances. */ @@ -95,16 +105,6 @@ #define CUSTOM_RAND_TYPE byte #endif -/* make sure Hash DRBG is enabled, unless WC_NO_HASHDRBG is defined - or CUSTOM_RAND_GENERATE_BLOCK is defined */ -#if !defined(WC_NO_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) - #undef HAVE_HASHDRBG - #define HAVE_HASHDRBG - #ifndef WC_RESEED_INTERVAL - #define WC_RESEED_INTERVAL (1000000) - #endif -#endif - #ifdef WC_RNG_LOCK_ATFORK /* On the heap so a memset of the WC_RNG cannot break the fork registry. An * unnamed semaphore: fork() copies it and sem_post() is the one unlock a From 73e5342661e284ff22cc175fa357a668af23b620 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 16:26:30 -0600 Subject: [PATCH 05/13] Give the CMake library HAVE_GETPID so it matches options.h, and tidy edges --- CMakeLists.txt | 3 +++ configure.ac | 4 ++++ wolfcrypt/src/random.c | 2 +- wolfcrypt/test/test.c | 5 ++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec95cab3a93..360fe727dbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,9 @@ check_function_exists("socket" HAVE_SOCKET) check_function_exists("strftime" HAVE_STRFTIME) check_function_exists("__atomic_fetch_add" HAVE_C___ATOMIC) check_function_exists("getpid" HAVE_GETPID) +if(HAVE_GETPID) + add_definitions("-DHAVE_GETPID") # the library and options.h must agree +endif() include(CheckSymbolExists) check_symbol_exists(isascii "ctype.h" HAVE_ISASCII) diff --git a/configure.ac b/configure.ac index e6bd600ab8c..c7b37733568 100644 --- a/configure.ac +++ b/configure.ac @@ -13355,6 +13355,10 @@ if test "$ENABLED_RNG_LOCK_THREADS" = "no" || test "$ENABLED_RNG" = "no" || \ test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" || \ { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } then + if test "x$enable_rng_lock" = "xyes" + then + AC_MSG_ERROR([--enable-rng-lock requires threads and the Hash DRBG, and none of selftest, FIPS before v7, a kernel module or a custom rand block]) + fi ENABLED_RNG_LOCK=no fi # a build that cannot carry the handlers drops them, unless asked explicitly diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index d38bb96b12e..5d495e09638 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -572,7 +572,7 @@ static void RngAtForkChild(void) } /* Registers the handlers once; the pin runs outside drbgStateMutex. */ -int wc_RngAtForkInit(void) +WOLFSSL_LOCAL int wc_RngAtForkInit(void) { int ret = LockDrbgState(); if (ret != 0) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e432d7761a5..8b3a15e7115 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28101,6 +28101,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) WC_RNG* mid = NULL; WC_RNG* third = NULL; int leak3 = 0; /* third may still be in use by its holder */ + unsigned int prevAlarm; (void)wc_rng_new_ex(&mid, NULL, 0, HEAP_HINT, INVALID_DEVID); (void)wc_rng_new_ex(&third, NULL, 0, HEAP_HINT, INVALID_DEVID); if (mid == NULL || third == NULL) { @@ -28111,11 +28112,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); } wc_rng_free(mid); /* the middle of three leaves the registry */ - alarm(30); /* a hung child or holder fails the run instead */ + prevAlarm = alarm(30); /* a hung child or holder fails the run */ ret = rng_fork_test(rng, &leak); if (ret == 0) ret = rng_fork_test(third, &leak3); alarm(0); + if (prevAlarm != 0) + alarm(prevAlarm); if (!leak3) wc_rng_free(third); if (ret != 0) From 163ebcb5253949367d8249eec5e6135cb41f7f30 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 17:08:06 -0600 Subject: [PATCH 06/13] Probe getpid through unistd.h in CMake, pin once, and scope the test watchdog --- CMakeLists.txt | 8 ++++---- configure.ac | 4 +++- examples/configs/user_settings_template.h | 5 +++-- wolfcrypt/src/random.c | 12 +++++++++--- wolfcrypt/src/wc_port.c | 1 + wolfcrypt/test/test.c | 6 ++++++ 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 360fe727dbb..1612865a2ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,13 +144,13 @@ check_function_exists("memset" HAVE_MEMSET) check_function_exists("socket" HAVE_SOCKET) check_function_exists("strftime" HAVE_STRFTIME) check_function_exists("__atomic_fetch_add" HAVE_C___ATOMIC) -check_function_exists("getpid" HAVE_GETPID) -if(HAVE_GETPID) - add_definitions("-DHAVE_GETPID") # the library and options.h must agree -endif() include(CheckSymbolExists) check_symbol_exists(isascii "ctype.h" HAVE_ISASCII) +check_symbol_exists(getpid "unistd.h" HAVE_GETPID) # pid_t comes with it +if(HAVE_GETPID) + add_definitions("-DHAVE_GETPID") # the library and options.h must agree +endif() include(CheckTypeSize) diff --git a/configure.ac b/configure.ac index c7b37733568..c9f1a821f6a 100644 --- a/configure.ac +++ b/configure.ac @@ -13348,9 +13348,11 @@ then AC_MSG_ERROR([--enable-rng-lock requires threads]) fi case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in - *-DCUSTOM_RAND_GENERATE_BLOCK*) ENABLED_RNG_LOCK=no ;; + *-DCUSTOM_RAND_GENERATE_BLOCK*) ENABLED_RNG_LOCK_CUSTOM=yes ;; + *) ENABLED_RNG_LOCK_CUSTOM=no ;; esac if test "$ENABLED_RNG_LOCK_THREADS" = "no" || test "$ENABLED_RNG" = "no" || \ + test "$ENABLED_RNG_LOCK_CUSTOM" = "yes" || \ test "$ENABLED_SELFTEST" = "yes" || test "x$ENABLED_HASHDRBG" != "xyes" || \ test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" || \ { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index ca8714aa81c..febaa164c67 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -444,8 +444,9 @@ extern "C" { * a heap, the lock above (not WC_RNG_NO_LOCK) and unnamed POSIX * semaphores (sem_init, so not macOS); the pin needs dladdr and dlopen, * -ldl on glibc before 2.34, and keeps the library mapped, since the - * handlers cannot be removed. Left out with entropy-memuse, the RNG - * bank, netRandom, selftest, FIPS before v7 or memory zero checking */ + * handlers cannot be removed. Left out with SINGLE_THREADED, + * entropy-memuse, the RNG bank, netRandom, selftest, FIPS before v7, + * memory zero checking, memory tracking or failure counting */ #define WC_RNG_ATFORK #endif diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 5d495e09638..55d7651d3ce 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -140,7 +140,7 @@ This library contains implementation for the random number generator. #include #ifdef WC_RNG_LOCK_ATFORK - #include + #include /* for the fork handlers, whatever the seed source */ #endif #ifdef WC_RNG_BANK_SUPPORT #include @@ -498,6 +498,7 @@ static int UnlockDrbgState(void) static WC_RNG_LOCK* rngList = NULL; /* every live lock, under rngListSem */ static sem_t rngListSem; static int rngAtForkSet = 0; /* handlers registered, never unregistered */ +static int rngImagePinned = 0; /* so a failed registration does not re-pin */ static int rngListDead = 0; /* registry unusable: every handler backs off */ /* sem_wait() is a cancellation point; a cancel here would strand the lock. */ @@ -577,7 +578,7 @@ WOLFSSL_LOCAL int wc_RngAtForkInit(void) int ret = LockDrbgState(); if (ret != 0) return ret; - if (!rngAtForkSet) { + if (!rngAtForkSet && !rngImagePinned) { /* pin outside the lock: dlopen() takes the loader lock. Racing * first callers may both pin, which is harmless. */ (void)UnlockDrbgState(); @@ -585,6 +586,7 @@ WOLFSSL_LOCAL int wc_RngAtForkInit(void) ret = LockDrbgState(); if (ret != 0) return ret; + rngImagePinned = 1; } if (!rngAtForkSet) { ret = (sem_init(&rngListSem, 0, 1) == 0) ? 0 : BAD_MUTEX_E; @@ -606,7 +608,11 @@ static int RngRegister(WC_RNG_LOCK* n) int ret = wc_RngAtForkInit(); if (ret != 0) return ret; - if (rngListDead || RngSemWait(&rngListSem) != 0) + if (rngListDead) { + WOLFSSL_MSG("RngRegister: registry dead since a fork"); + return BAD_MUTEX_E; + } + if (RngSemWait(&rngListSem) != 0) return BAD_MUTEX_E; ret = (sem_init(&n->sem, 0, 1) == 0) ? 0 : BAD_MUTEX_E; if (ret == 0) { diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 27c5ddd7cb4..1ecdf04dd16 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -582,6 +582,7 @@ int wolfCrypt_Init(void) } #endif #ifdef WC_RNG_LOCK_ATFORK + /* here, before the app has threads, so no fork can race it */ ret = wc_RngAtForkInit(); if (ret != 0) { WOLFSSL_MSG("RNG fork handler registration failed"); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 8b3a15e7115..35c857ffeae 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28101,7 +28101,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) WC_RNG* mid = NULL; WC_RNG* third = NULL; int leak3 = 0; /* third may still be in use by its holder */ + #ifndef NO_MAIN_DRIVER unsigned int prevAlarm; + #endif (void)wc_rng_new_ex(&mid, NULL, 0, HEAP_HINT, INVALID_DEVID); (void)wc_rng_new_ex(&third, NULL, 0, HEAP_HINT, INVALID_DEVID); if (mid == NULL || third == NULL) { @@ -28112,13 +28114,17 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); } wc_rng_free(mid); /* the middle of three leaves the registry */ + #ifndef NO_MAIN_DRIVER prevAlarm = alarm(30); /* a hung child or holder fails the run */ + #endif ret = rng_fork_test(rng, &leak); if (ret == 0) ret = rng_fork_test(third, &leak3); + #ifndef NO_MAIN_DRIVER alarm(0); if (prevAlarm != 0) alarm(prevAlarm); + #endif if (!leak3) wc_rng_free(third); if (ret != 0) From 29aad4f505c93bfe540b487075c327a2ca824dee Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 18:39:51 -0600 Subject: [PATCH 07/13] Guard the compat fork on its pipe and churn the registry during the fork tests --- configure.ac | 2 +- tests/api/test_ossl_rand.c | 8 +++--- wolfcrypt/test/test.c | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index c9f1a821f6a..383337123e6 100644 --- a/configure.ac +++ b/configure.ac @@ -13393,7 +13393,7 @@ then then saved_LIBS="$LIBS" AC_SEARCH_LIBS([pthread_atfork], [pthread]) - LIBS="$saved_LIBS" + LIBS="$saved_LIBS" # detection only: AX_PTHREAD already added the library if test "$ac_cv_search_pthread_atfork" = "no" then RNG_ATFORK_NEEDS="pthread_atfork" diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index e82e34a8502..039bcbe5169 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -186,6 +186,7 @@ int test_wolfSSL_RAND_bytes(void) byte randbuf[8] = {0}; int pipefds[2] = {0}; pid_t pid = 0; + int piped; #endif /* sanity check */ @@ -215,8 +216,9 @@ int test_wolfSSL_RAND_bytes(void) /* No global methods set. */ ExpectIntEQ(RAND_seed(seed, sizeof(seed)), 1); - ExpectIntEQ(pipe(pipefds), 0); - pid = fork(); + piped = (pipe(pipefds) == 0); + ExpectIntEQ(piped, 1); + pid = piped ? fork() : -1; ExpectIntGE(pid, 0); if (pid == 0) { ssize_t n_written = 0; @@ -251,7 +253,7 @@ int test_wolfSSL_RAND_bytes(void) ExpectIntEQ(reaped && WIFEXITED(waitstatus) && WEXITSTATUS(waitstatus) == 0, 1); } - else { + else if (piped) { close(pipefds[0]); close(pipefds[1]); } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 35c857ffeae..2db813c7b18 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28065,6 +28065,35 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng, int* leak) WC_FREE_VAR(child, HEAP_HINT); return ret; } +struct rng_churn_args { + int ret; /* first failure, if any */ + long ns; /* how long to keep registering and freeing */ +}; + +/* Registers and frees instances while the fork tests run, so the registry + * changes under the handlers. */ +static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_churn(void* arg) +{ + struct rng_churn_args* a = (struct rng_churn_args*)arg; + struct timespec start, now; + WC_RNG* r; + + if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) + WOLFSSL_RETURN_FROM_THREAD(0); + do { + r = NULL; + a->ret = wc_rng_new_ex(&r, NULL, 0, HEAP_HINT, INVALID_DEVID); + if (a->ret != 0) + break; + wc_rng_free(r); + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + break; + } while (now.tv_sec - start.tv_sec < 2 && + (now.tv_sec - start.tv_sec) * 1000000000L + + (now.tv_nsec - start.tv_nsec) < a->ns); + WOLFSSL_RETURN_FROM_THREAD(0); +} + #endif /* WC_TEST_RNG_FORK */ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) @@ -28100,6 +28129,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) * survivor. */ WC_RNG* mid = NULL; WC_RNG* third = NULL; + struct rng_churn_args* c = NULL; + THREAD_TYPE churn = INVALID_THREAD_VAL; /* joined only if started */ + int churning = 0; int leak3 = 0; /* third may still be in use by its holder */ #ifndef NO_MAIN_DRIVER unsigned int prevAlarm; @@ -28114,6 +28146,16 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); } wc_rng_free(mid); /* the middle of three leaves the registry */ + c = (struct rng_churn_args*)XMALLOC(sizeof(*c), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (c == NULL) { + wc_rng_free(third); + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); + } + c->ret = 0; + c->ns = 6 * WC_RNG_FORK_HOLD_NS; /* outlasts both fork tests */ + if (wolfSSL_NewThread(&churn, &rng_fork_test_churn, c) == 0) + churning = 1; #ifndef NO_MAIN_DRIVER prevAlarm = alarm(30); /* a hung child or holder fails the run */ #endif @@ -28125,6 +28167,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) if (prevAlarm != 0) alarm(prevAlarm); #endif + if (churning && wolfSSL_JoinThread(churn) != 0) { + leak = 1; /* the churn thread may still use c */ + } + else { + if (ret == 0 && (!churning || c->ret != 0)) + ret = churning ? WC_TEST_RET_ENC_EC(c->ret) + : WC_TEST_RET_ENC_NC; + XFREE(c, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } if (!leak3) wc_rng_free(third); if (ret != 0) From 2ac5d817a944b128a439419de336ffcaab68e827 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 19:23:06 -0600 Subject: [PATCH 08/13] Keep HAVE_GETPID out of CMake user_settings builds and scan EXTRA_CFLAGS --- .github/configs/os-check-linux.json | 3 +++ CMakeLists.txt | 3 ++- configure.ac | 6 +++--- examples/configs/user_settings_template.h | 2 ++ wolfcrypt/test/test.c | 4 +++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 38ad5b25a4a..4851d4ca1f2 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -289,6 +289,9 @@ {"name": "no-tls-cryptocb-aesgcm-setkey-free", "minutes": 1.3, "configure": ["--disable-tls", "--enable-cryptocb", "--enable-aesgcm", "CPPFLAGS=-DWOLF_CRYPTO_CB_AES_SETKEY -DWOLF_CRYPTO_CB_FREE"]}, +{"name": "rng-bank-lock", "minutes": 1.3, + "comment": "The RNG bank with the per-instance lock: bank builds still hand out plain instances.", + "configure": ["--enable-rng-bank", "--enable-opensslextra"]}, {"name": "she-std-cmac", "minutes": 1.2, "configure": ["--enable-she=standard", "--enable-cmac"]}, {"name": "no-verify-oid-fpki", "minutes": 1.2, diff --git a/CMakeLists.txt b/CMakeLists.txt index 1612865a2ef..897eb019a5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,7 +149,7 @@ include(CheckSymbolExists) check_symbol_exists(isascii "ctype.h" HAVE_ISASCII) check_symbol_exists(getpid "unistd.h" HAVE_GETPID) # pid_t comes with it if(HAVE_GETPID) - add_definitions("-DHAVE_GETPID") # the library and options.h must agree + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_GETPID") # as options.h says endif() include(CheckTypeSize) @@ -4238,6 +4238,7 @@ generate_build_flags() if(WOLFSSL_USER_SETTINGS) # Replace all options and just use WOLFSSL_USER_SETTINGS set(WOLFSSL_DEFINITIONS "-DWOLFSSL_USER_SETTINGS") + set(HAVE_GETPID OFF) # user_settings.h decides; keeps options.h in step endif() if(WOLFSSL_USER_SETTINGS_ASM) diff --git a/configure.ac b/configure.ac index 383337123e6..c2ac297ae8e 100644 --- a/configure.ac +++ b/configure.ac @@ -13335,7 +13335,7 @@ fi # RNG lock and fork handlers need threads; the handlers need everything the # WC_RNG_LOCK_ATFORK gate in random.h asks for -case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in +case "$AM_CFLAGS $CPPFLAGS $CFLAGS $EXTRA_CFLAGS" in *-DSINGLE_THREADED*) ENABLED_RNG_LOCK_THREADS=no ;; *) ENABLED_RNG_LOCK_THREADS=yes ;; esac @@ -13347,7 +13347,7 @@ if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG_LOCK_THREADS" = "no" then AC_MSG_ERROR([--enable-rng-lock requires threads]) fi -case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in +case "$AM_CFLAGS $CPPFLAGS $CFLAGS $EXTRA_CFLAGS" in *-DCUSTOM_RAND_GENERATE_BLOCK*) ENABLED_RNG_LOCK_CUSTOM=yes ;; *) ENABLED_RNG_LOCK_CUSTOM=no ;; esac @@ -13377,7 +13377,7 @@ then then RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }threads and no kernel module" fi - case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in + case "$AM_CFLAGS $CPPFLAGS $CFLAGS $EXTRA_CFLAGS" in *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*|*-DWOLFSSL_CHECK_MEM_ZERO*|*-DWOLFSSL_TRACK_MEMORY*|*-DWOLFSSL_MEM_FAIL_COUNT*|*-DWOLFSSL_USER_MUTEX*) ENABLED_RNG_ATFORK_FLAGS=no ;; *) ENABLED_RNG_ATFORK_FLAGS=yes ;; diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index febaa164c67..879de0a555c 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -437,6 +437,7 @@ extern "C" { #endif #if 0 /* Threaded build that never shares one WC_RNG between threads */ + #undef WC_RNG_NO_LOCK #define WC_RNG_NO_LOCK #endif #if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ @@ -447,6 +448,7 @@ extern "C" { * handlers cannot be removed. Left out with SINGLE_THREADED, * entropy-memuse, the RNG bank, netRandom, selftest, FIPS before v7, * memory zero checking, memory tracking or failure counting */ + #undef WC_RNG_ATFORK #define WC_RNG_ATFORK #endif diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 2db813c7b18..61a91e0b980 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28168,7 +28168,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) alarm(prevAlarm); #endif if (churning && wolfSSL_JoinThread(churn) != 0) { - leak = 1; /* the churn thread may still use c */ + c = NULL; /* leaked: the churn thread may still use it */ + if (ret == 0) + ret = WC_TEST_RET_ENC_NC; } else { if (ret == 0 && (!churning || c->ret != 0)) From 07f90cbbfa73f3b3b4e95b424f611b4e778662c8 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Tue, 8 Sep 2026 20:03:02 -0600 Subject: [PATCH 09/13] Require thread cancellation in the fork handler probes: Android has none --- CMakeLists.txt | 5 ++++- configure.ac | 7 +++++-- examples/configs/user_settings_template.h | 3 ++- wolfssl/wolfcrypt/random.h | 6 +++--- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 897eb019a5f..d22d691959a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4037,6 +4037,7 @@ if(WOLFSSL_RNG_ATFORK) #include #include #include + #include #ifdef __APPLE__ #error macOS has only named semaphores, which a forked child shares #endif @@ -4044,8 +4045,10 @@ if(WOLFSSL_RNG_ATFORK) Dl_info info; const char* name; sem_t s; + int old; if (sem_init(&s, 0, 1) == 0) (void)sem_post(&s); + (void)pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old); if (dladdr((void*)(uintptr_t)main, &info) != 0) { name = info.dli_fname; if (name != NULL && name[0] != 0) @@ -4058,7 +4061,7 @@ if(WOLFSSL_RNG_ATFORK) if(NOT WOLFSSL_HAVE_PTHREAD_ATFORK) set(RNG_ATFORK_NEEDS "pthread_atfork") elseif(NOT WOLFSSL_HAVE_DLADDR_PIN) - set(RNG_ATFORK_NEEDS "dladdr, dlopen and unnamed POSIX semaphores") + set(RNG_ATFORK_NEEDS "dladdr, dlopen, unnamed semaphores and thread cancellation") endif() endif() if(RNG_ATFORK_NEEDS) diff --git a/configure.ac b/configure.ac index c2ac297ae8e..8fb6245b61c 100644 --- a/configure.ac +++ b/configure.ac @@ -13405,7 +13405,7 @@ then # hides dladdr or lacks the RTLD flags saved_LIBS="$LIBS" AC_SEARCH_LIBS([dladdr], [dl]) - AC_MSG_CHECKING([whether dladdr, dlopen and unnamed POSIX semaphores are available]) + AC_MSG_CHECKING([whether dladdr, dlopen, unnamed semaphores and thread cancellation are available]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #if defined(__linux__) || defined(__ANDROID__) || \ defined(__CYGWIN__) || defined(__MSYS__) @@ -13415,6 +13415,7 @@ then #include #include #include + #include #ifdef __APPLE__ #error "macOS has only named semaphores, which a forked child shares" #endif @@ -13422,8 +13423,10 @@ then Dl_info info; const char* name; sem_t s; + int old; if (sem_init(&s, 0, 1) == 0) (void)sem_post(&s); + (void)pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old); if (dladdr((void*)(uintptr_t)main, &info) != 0) { name = info.dli_fname; if (name != NULL && name[0] != '\0') @@ -13432,7 +13435,7 @@ then ]])], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) - RNG_ATFORK_NEEDS="dladdr, dlopen and unnamed POSIX semaphores"]) + RNG_ATFORK_NEEDS="dladdr, dlopen, unnamed semaphores and thread cancellation"]) LIBS="$saved_LIBS" fi if test -n "$RNG_ATFORK_NEEDS" diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index 879de0a555c..58599160109 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -443,7 +443,8 @@ extern "C" { #if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ /* configure probes for this; here it is asserted. Needs pthreads, * a heap, the lock above (not WC_RNG_NO_LOCK) and unnamed POSIX - * semaphores (sem_init, so not macOS); the pin needs dladdr and dlopen, + * semaphores (sem_init, so not macOS) and pthread_setcancelstate (so + * not Android); the pin needs dladdr and dlopen, * -ldl on glibc before 2.34, and keeps the library mapped, since the * handlers cannot be removed. Left out with SINGLE_THREADED, * entropy-memuse, the RNG bank, netRandom, selftest, FIPS before v7, diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 2c20cdb84ed..8d0377e8b75 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -56,9 +56,9 @@ #endif /* pthread_atfork handlers so a forked child can keep using its WC_RNG. - * configure and CMake define WC_RNG_ATFORK where the dlclose pin and unnamed - * semaphores exist; builds whose locks the handlers cannot cover are left - * out. */ + * configure and CMake define WC_RNG_ATFORK where the dlclose pin, unnamed + * semaphores and thread cancellation exist; builds whose locks the handlers + * cannot cover are left out. */ #if defined(WC_RNG_HAVE_LOCK) && defined(WOLFSSL_PTHREADS) && \ defined(WC_RNG_ATFORK) && !defined(__APPLE__) && \ !defined(WOLFSSL_NO_MALLOC) && !defined(HAVE_ENTROPY_MEMUSE) && \ From 7513376bc683a160fa0fbc8b02e834cba1fbcf23 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 9 Sep 2026 05:47:43 -0600 Subject: [PATCH 10/13] Treat a failed thread join in the RNG tests as an error, not a reason to leak --- wolfcrypt/test/test.c | 51 ++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 61a91e0b980..85454fd6e1b 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -27963,9 +27963,8 @@ static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_holder(void* arg) } /* fork() while another thread holds the lock: the child must finish with a - * different next block. The hold is best effort; the checks hold either way. - * Sets leak when the holder could not be joined. */ -static wc_test_ret_t rng_fork_test(WC_RNG* rng, int* leak) + * different next block. The hold is best effort; the checks hold anyway. */ +static wc_test_ret_t rng_fork_test(WC_RNG* rng) { WC_DECLARE_VAR(parent, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); WC_DECLARE_VAR(child, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); @@ -28042,12 +28041,8 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng, int* leak) (void)kill(pid, SIGKILL); (void)waitpid(pid, NULL, 0); } - if (started && (wolfSSL_JoinThread(holder) != 0)) { - *leak = 1; /* the holder still uses h, rng and its pipe */ - h = NULL; - if (ret == 0) - ret = WC_TEST_RET_ENC_NC; - } + if (started && (wolfSSL_JoinThread(holder) != 0) && ret == 0) + ret = WC_TEST_RET_ENC_NC; if (piped >= 1) { if (fd[0] >= 0) close(fd[0]); @@ -28055,8 +28050,7 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng, int* leak) close(fd[1]); } if (piped >= 2) { - if (h != NULL || !started) - close(hfd[0]); /* kept open for a lost holder: no SIGPIPE */ + close(hfd[0]); if (!started) close(hfd[1]); } @@ -28102,7 +28096,6 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) THREAD_TYPE threads[WC_RNG_THREAD_TEST_THREADS]; struct rng_thread_test_args* args = NULL; byte* out = NULL; - int leak = 0; /* rng, args or out may still be in use by a thread */ int started = 0; int nblocks; int i, j; @@ -28110,8 +28103,6 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) WOLFSSL_ENTER("random_thread_test"); - /* Everything a thread can reach is on the heap, so a thread that cannot - * be joined is leaked instead of left running over a dead frame. */ out = (byte*)XMALLOC((size_t)WC_RNG_THREAD_TEST_BLOCKS * WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -28132,7 +28123,6 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) struct rng_churn_args* c = NULL; THREAD_TYPE churn = INVALID_THREAD_VAL; /* joined only if started */ int churning = 0; - int leak3 = 0; /* third may still be in use by its holder */ #ifndef NO_MAIN_DRIVER unsigned int prevAlarm; #endif @@ -28159,27 +28149,20 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) #ifndef NO_MAIN_DRIVER prevAlarm = alarm(30); /* a hung child or holder fails the run */ #endif - ret = rng_fork_test(rng, &leak); + ret = rng_fork_test(rng); if (ret == 0) - ret = rng_fork_test(third, &leak3); + ret = rng_fork_test(third); #ifndef NO_MAIN_DRIVER alarm(0); if (prevAlarm != 0) alarm(prevAlarm); #endif - if (churning && wolfSSL_JoinThread(churn) != 0) { - c = NULL; /* leaked: the churn thread may still use it */ - if (ret == 0) - ret = WC_TEST_RET_ENC_NC; - } - else { - if (ret == 0 && (!churning || c->ret != 0)) - ret = churning ? WC_TEST_RET_ENC_EC(c->ret) - : WC_TEST_RET_ENC_NC; - XFREE(c, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - } - if (!leak3) - wc_rng_free(third); + if (churning && wolfSSL_JoinThread(churn) != 0 && ret == 0) + ret = WC_TEST_RET_ENC_NC; + else if (ret == 0 && (!churning || c->ret != 0)) + ret = churning ? WC_TEST_RET_ENC_EC(c->ret) : WC_TEST_RET_ENC_NC; + XFREE(c, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_rng_free(third); if (ret != 0) goto out_free; } @@ -28222,10 +28205,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) for (i = 0; i < started; i++) { if (wolfSSL_JoinThread(threads[i]) != 0) - leak = 1; + ret = WC_TEST_RET_ENC_NC; } - if (leak) - ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); + if (ret != 0) + goto out_free; /* Worker errors first, whatever the thread count. */ for (i = 0; i < started; i++) { @@ -28252,8 +28235,6 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) } out_free: - if (leak) - return ret; /* a thread may still use rng, args or out */ if (rng != NULL) wc_rng_free(rng); XFREE(args, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); From e63e32a38ed7dcac7c4fbe8ddac92224d8b24642 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 9 Sep 2026 07:27:33 -0600 Subject: [PATCH 11/13] Lock the hardware and async RNG backends and make the lock tests prove the wait --- doc/dox_comments/header_files/random.h | 8 +- wolfcrypt/src/random.c | 65 +++++++++++--- wolfcrypt/test/test.c | 114 ++++++++++++++++++++----- wolfssl/wolfcrypt/random.h | 1 + 4 files changed, 153 insertions(+), 35 deletions(-) diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index f1bfb6fdbc2..80c48249a05 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -58,9 +58,11 @@ int wc_FreeNetRandom(void); One WC_RNG may be shared between threads: each generate and reseed holds the instance lock. WC_RNG_NO_LOCK (configure --disable-rng-lock) leaves - the lock out. A seed or hash crypto callback runs with the lock held, so - it must not use the RNG API. wc_InitRng*() and wc_FreeRng() do not lock; - initialize only a new or freed WC_RNG, with no other thread using it. + the lock out. Every backend but a crypto callback runs with the lock + held; a callback answers first, so it may fall back to the same instance. + A seed callback runs with the lock held and must not use the RNG API. + wc_InitRng*() and wc_FreeRng() do not lock; initialize only a new or + freed WC_RNG, with no other thread using it. POSIX lets a forked child of a threaded process only exec. Where the build has pthread_atfork(), unnamed POSIX semaphores and the dladdr() diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 55d7651d3ce..6b3146a705e 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -728,17 +728,44 @@ static void RngLockFree(WC_RNG* rng) } } +/* Cancellation stays off while the lock is held, where the platform has + * cancellation at all: a reseed reads a device, a cancellation point. */ static int RngLockEnter(WC_RNG* rng) { - if (rng->lockInited && wc_LockMutex(&rng->lock) != 0) +#ifdef PTHREAD_CANCEL_DISABLE + int old = PTHREAD_CANCEL_ENABLE; +#endif + if (!rng->lockInited) + return 0; +#ifdef PTHREAD_CANCEL_DISABLE + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); +#endif + if (wc_LockMutex(&rng->lock) != 0) { + #ifdef PTHREAD_CANCEL_DISABLE + (void)pthread_setcancelstate(old, NULL); + #endif return BAD_MUTEX_E; + } +#ifdef PTHREAD_CANCEL_DISABLE + rng->lockCancel = old; +#endif return 0; } static void RngLockExit(WC_RNG* rng) { - if (rng->lockInited) - (void)wc_UnLockMutex(&rng->lock); +#ifdef PTHREAD_CANCEL_DISABLE + int old; +#endif + if (!rng->lockInited) + return; +#ifdef PTHREAD_CANCEL_DISABLE + old = rng->lockCancel; /* read before the unlock hands the slot on */ +#endif + (void)wc_UnLockMutex(&rng->lock); +#ifdef PTHREAD_CANCEL_DISABLE + (void)pthread_setcancelstate(old, NULL); +#endif } #else #define RngLockEnter(rng) 0 @@ -2824,6 +2851,7 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) return 0; #ifdef WOLF_CRYPTO_CB + /* before the lock: a callback may fall back to this same instance */ #ifndef WOLF_CRYPTO_CB_FIND if (rng->devId != INVALID_DEVID) #endif @@ -2835,22 +2863,35 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) } #endif + ret = RngLockEnter(rng); /* held across every other backend */ + if (ret != 0) + return ret; + #ifdef HAVE_INTEL_RDRAND - if (IS_INTEL_RDRAND(intel_flags)) - return wc_GenerateRand_IntelRD(NULL, output, sz); + if (IS_INTEL_RDRAND(intel_flags)) { + ret = wc_GenerateRand_IntelRD(NULL, output, sz); + RngLockExit(rng); + return ret; + } #endif #if defined(WOLFSSL_SILABS_SE_ACCEL) && defined(WOLFSSL_SILABS_TRNG) - return silabs_GenerateRand(output, sz); + ret = silabs_GenerateRand(output, sz); + RngLockExit(rng); + return ret; #endif #if defined(WOLFSSL_ASYNC_CRYPT) if (rng->asyncDev.marker == WOLFSSL_ASYNC_MARKER_RNG) { /* these are blocking */ #ifdef HAVE_CAVIUM - return NitroxRngGenerateBlock(rng, output, sz); + ret = NitroxRngGenerateBlock(rng, output, sz); + RngLockExit(rng); + return ret; #elif defined(HAVE_INTEL_QA) && defined(QAT_ENABLE_RNG) - return IntelQaDrbg(&rng->asyncDev, output, sz); + ret = IntelQaDrbg(&rng->asyncDev, output, sz); + RngLockExit(rng); + return ret; #else /* simulator not supported */ #endif @@ -2868,12 +2909,10 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) #else #ifdef HAVE_HASHDRBG - if (sz > RNG_MAX_BLOCK_LEN) + if (sz > RNG_MAX_BLOCK_LEN) { + RngLockExit(rng); return BAD_FUNC_ARG; - - ret = RngLockEnter(rng); - if (ret != 0) - return ret; + } if (rng->status != DRBG_OK) { RngLockExit(rng); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 85454fd6e1b..d0224535c56 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -27925,9 +27925,19 @@ static THREAD_RETURN WOLFSSL_THREAD rng_thread_test_worker(void* arg) #ifdef WC_TEST_RNG_FORK #define WC_RNG_FORK_HOLD_NS 50000000L +/* Elapsed nanoseconds, capped at two seconds so nothing overflows. */ +static long rng_test_elapsed_ns(const struct timespec* a, + const struct timespec* b) +{ + if (b->tv_sec - a->tv_sec >= 2) + return 2000000000L; + return (b->tv_sec - a->tv_sec) * 1000000000L + (b->tv_nsec - a->tv_nsec); +} + struct rng_fork_holder_args { WC_RNG* rng; - int fd; /* gets one byte once the lock is held */ + int fd; /* gets one byte once the lock is held */ + int rfd; /* the go byte arrives here; the hold is timed from it */ }; /* Holds the lock while the other thread enters fork(), as a generate in @@ -27939,6 +27949,7 @@ static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_holder(void* arg) struct rng_fork_holder_args* a = (struct rng_fork_holder_args*)arg; struct timespec start, now; byte held = 1; + byte go = 0; int rc = -1; if (a->rng->lock != NULL) { @@ -27947,18 +27958,18 @@ static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_holder(void* arg) } while (rc != 0 && errno == EINTR); } if (rc == 0) { - if (write(a->fd, &held, 1) == 1 && + /* hold for the full time only once the tester says it is timing */ + if (write(a->fd, &held, 1) == 1 && read(a->rfd, &go, 1) == 1 && clock_gettime(CLOCK_MONOTONIC, &start) == 0) { do { if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) break; - } while (now.tv_sec - start.tv_sec < 2 && /* no overflow */ - (now.tv_sec - start.tv_sec) * 1000000000L + - (now.tv_nsec - start.tv_nsec) < WC_RNG_FORK_HOLD_NS); + } while (rng_test_elapsed_ns(&start, &now) < WC_RNG_FORK_HOLD_NS); } (void)sem_post(&a->rng->lock->sem); } close(a->fd); /* EOF if the lock was never held */ + close(a->rfd); WOLFSSL_RETURN_FROM_THREAD(0); } @@ -27973,11 +27984,14 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng) wc_test_ret_t ret = 0; int fd[2]; int hfd[2]; + int gfd[2]; int piped = 0; int started = 0; pid_t pid = -1; int status = 0; byte held = 0; + byte go = 1; + struct timespec t0, t1; WC_ALLOC_VAR(parent, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); WC_ALLOC_VAR(child, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); @@ -27992,16 +28006,24 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng) if (pipe(hfd) != 0) ERROR_OUT(WC_TEST_RET_ENC_NC, done); piped = 2; + if (pipe(gfd) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + piped = 3; h->rng = rng; h->fd = hfd[1]; + h->rfd = gfd[0]; if (wolfSSL_NewThread(&holder, &rng_fork_test_holder, h) != 0) ERROR_OUT(WC_TEST_RET_ENC_NC, done); started = 1; if (read(hfd[0], &held, 1) != 1) ERROR_OUT(WC_TEST_RET_ENC_NC, done); + (void)clock_gettime(CLOCK_MONOTONIC, &t0); /* before the go byte */ + if (write(gfd[1], &go, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); pid = fork(); + (void)clock_gettime(CLOCK_MONOTONIC, &t1); if (pid == 0) { if (wc_RNG_GenerateBlock(rng, child, WC_RNG_THREAD_TEST_BLKSZ) != 0) _exit(1); @@ -28018,6 +28040,9 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng) fd[1] = -1; if (pid < 0) ERROR_OUT(WC_TEST_RET_ENC_NC, done); + /* prepare had to wait for the holder, so fork() took most of the hold */ + if (rng_test_elapsed_ns(&t0, &t1) < WC_RNG_FORK_HOLD_NS / 2) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); if (read(fd[0], child, WC_RNG_THREAD_TEST_BLKSZ) != (ssize_t)WC_RNG_THREAD_TEST_BLKSZ) @@ -28041,6 +28066,8 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng) (void)kill(pid, SIGKILL); (void)waitpid(pid, NULL, 0); } + if (piped >= 3) + close(gfd[1]); /* EOF frees a holder still waiting for go */ if (started && (wolfSSL_JoinThread(holder) != 0) && ret == 0) ret = WC_TEST_RET_ENC_NC; if (piped >= 1) { @@ -28054,11 +28081,68 @@ static wc_test_ret_t rng_fork_test(WC_RNG* rng) if (!started) close(hfd[1]); } + if (piped >= 3 && !started) + close(gfd[0]); XFREE(h, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR(parent, HEAP_HINT); WC_FREE_VAR(child, HEAP_HINT); return ret; } +/* A generate on a held instance must not finish until the holder lets go. */ +static wc_test_ret_t rng_lock_wait_test(WC_RNG* rng) +{ + WC_DECLARE_VAR(blk, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + struct rng_fork_holder_args* h = NULL; + THREAD_TYPE holder = INVALID_THREAD_VAL; + struct timespec t0, t1; + wc_test_ret_t ret = 0; + int hfd[2] = { -1, -1 }; + int gfd[2] = { -1, -1 }; + int started = 0; + byte held = 0; + byte go = 1; + + WC_ALLOC_VAR(blk, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + h = (struct rng_fork_holder_args*)XMALLOC(sizeof(*h), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if ((! WC_VAR_OK(blk)) || h == NULL || pipe(hfd) != 0 || pipe(gfd) != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), done); + h->rng = rng; + h->fd = hfd[1]; + h->rfd = gfd[0]; + if (wolfSSL_NewThread(&holder, &rng_fork_test_holder, h) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + started = 1; + if (read(hfd[0], &held, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + (void)clock_gettime(CLOCK_MONOTONIC, &t0); /* before the go byte */ + if (write(gfd[1], &go, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + ret = wc_RNG_GenerateBlock(rng, blk, WC_RNG_THREAD_TEST_BLKSZ); + (void)clock_gettime(CLOCK_MONOTONIC, &t1); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), done); + if (rng_test_elapsed_ns(&t0, &t1) < WC_RNG_FORK_HOLD_NS / 2) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); /* it did not wait */ + +done: + if (gfd[1] >= 0) + close(gfd[1]); /* EOF frees a holder still waiting for go */ + if (started && (wolfSSL_JoinThread(holder) != 0) && ret == 0) + ret = WC_TEST_RET_ENC_NC; + if (hfd[0] >= 0) + close(hfd[0]); + if (!started) { + if (hfd[1] >= 0) + close(hfd[1]); + if (gfd[0] >= 0) + close(gfd[0]); + } + XFREE(h, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR(blk, HEAP_HINT); + return ret; +} + struct rng_churn_args { int ret; /* first failure, if any */ long ns; /* how long to keep registering and freeing */ @@ -28082,9 +28166,7 @@ static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_churn(void* arg) wc_rng_free(r); if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) break; - } while (now.tv_sec - start.tv_sec < 2 && - (now.tv_sec - start.tv_sec) * 1000000000L + - (now.tv_nsec - start.tv_nsec) < a->ns); + } while (rng_test_elapsed_ns(&start, &now) < a->ns); WOLFSSL_RETURN_FROM_THREAD(0); } @@ -28123,9 +28205,6 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) struct rng_churn_args* c = NULL; THREAD_TYPE churn = INVALID_THREAD_VAL; /* joined only if started */ int churning = 0; - #ifndef NO_MAIN_DRIVER - unsigned int prevAlarm; - #endif (void)wc_rng_new_ex(&mid, NULL, 0, HEAP_HINT, INVALID_DEVID); (void)wc_rng_new_ex(&third, NULL, 0, HEAP_HINT, INVALID_DEVID); if (mid == NULL || third == NULL) { @@ -28136,6 +28215,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); } wc_rng_free(mid); /* the middle of three leaves the registry */ + ret = rng_lock_wait_test(rng); + if (ret != 0) { + wc_rng_free(third); + goto out_free; + } c = (struct rng_churn_args*)XMALLOC(sizeof(*c), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); if (c == NULL) { @@ -28146,17 +28230,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) c->ns = 6 * WC_RNG_FORK_HOLD_NS; /* outlasts both fork tests */ if (wolfSSL_NewThread(&churn, &rng_fork_test_churn, c) == 0) churning = 1; - #ifndef NO_MAIN_DRIVER - prevAlarm = alarm(30); /* a hung child or holder fails the run */ - #endif ret = rng_fork_test(rng); if (ret == 0) ret = rng_fork_test(third); - #ifndef NO_MAIN_DRIVER - alarm(0); - if (prevAlarm != 0) - alarm(prevAlarm); - #endif if (churning && wolfSSL_JoinThread(churn) != 0 && ret == 0) ret = WC_TEST_RET_ENC_NC; else if (ret == 0 && (!churning || c->ret != 0)) diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 8d0377e8b75..f7633df66f0 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -480,6 +480,7 @@ struct WC_RNG { #elif defined(WC_RNG_HAVE_LOCK) wolfSSL_Mutex lock; /* serializes generate and reseed */ byte lockInited; /* nonzero once lock exists */ + int lockCancel; /* the holder's cancel state, back on exit */ #endif }; From 0051babbade02bfc0df4aa6435409e03bcbd02b4 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 9 Sep 2026 08:23:07 -0600 Subject: [PATCH 12/13] Define the no-op RNG lock helpers for builds without the Hash DRBG --- wolfcrypt/src/random.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 6b3146a705e..87acb2b341a 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -2196,6 +2196,10 @@ int wc_Sha512Drbg_IsDisabled(void) } #endif /* WOLFSSL_DRBG_SHA512 */ #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ +#else + /* no Hash DRBG, so no lock for the backends to hold */ + #define RngLockEnter(rng) 0 + #define RngLockExit(rng) WC_DO_NOTHING #endif /* HAVE_HASHDRBG */ /* End NIST DRBG Code */ From 06cb0a74f97726d2993b51cb0aabfb6830ceeddb Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 9 Sep 2026 10:53:06 -0600 Subject: [PATCH 13/13] Add PTHREAD_CANCEL_DISABLE to the known macro list --- .wolfssl_known_macro_extras | 1 + 1 file changed, 1 insertion(+) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index b79d99f0177..fdf529a69ba 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -563,6 +563,7 @@ PKA_SR_OPERRF PLATFORMIO PLUTON_CRYPTO_ECC PRINT_SESSION_STATS +PTHREAD_CANCEL_DISABLE PTHREAD_STACK_MIN QAT_ENABLE_HASH QAT_ENABLE_RNG