From d7835ed422bd7416167db320588fb8040fdcbb6c Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Fri, 4 Sep 2026 23:30:01 -0600 Subject: [PATCH 01/15] Add a per-instance lock so one WC_RNG can be shared between threads, with opt-in fork handlers --- .github/configs/os-check-linux.json | 9 + CMakeLists.txt | 35 +++ cmake/options.h.in | 4 + configure.ac | 83 ++++++ doc/dox_comments/header_files/random.h | 50 +++- examples/configs/user_settings_template.h | 8 + wolfcrypt/src/random.c | 225 +++++++++++++- wolfcrypt/src/wc_port.c | 7 + wolfcrypt/test/test.c | 342 ++++++++++++++++++++++ wolfcrypt/test/test.h | 22 ++ wolfssl/wolfcrypt/random.h | 54 ++++ 11 files changed, 832 insertions(+), 7 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index d3ec20c7331..963710152e5 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -287,7 +287,16 @@ "configure": ["--enable-she=standard", "--enable-cmac"]}, {"name": "no-verify-oid-fpki", "minutes": 1.2, "configure": ["CPPFLAGS=-DNO_VERIFY_OID -DWOLFSSL_FPKI"]}, +{"name": "rng-atfork", "minutes": 1.2, + "comment": "pthread_atfork handlers for a forked child that keeps using its WC_RNG; runs the fork half of the RNG thread test.", + "configure": ["--enable-rng-atfork"]}, +{"name": "rng-atfork-no-getpid", "minutes": 1.2, + "comment": "Same handlers without the pid check, so the child handler alone must make the forked child reseed.", + "configure": ["--enable-rng-atfork", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, {"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/CMakeLists.txt b/CMakeLists.txt index 8263cd43ab9..a7b8508a523 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3765,6 +3765,16 @@ 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 lock defaults to yes, so a yes cannot be told from the default; the +# header leaves it 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 +4003,31 @@ if(NOT WOLFSSL_STATICMEMORY STREQUAL "no") endif() endif() +# RNG fork handlers (a forked child keeps using its WC_RNG). Below the +# static memory option because it reads it. +add_option("WOLFSSL_RNG_ATFORK" + "Enable pthread_atfork handlers so a forked child can keep using a WC_RNG (default: disabled)" + "no" "yes;no") +if(WOLFSSL_RNG_ATFORK) + if(WOLFSSL_SINGLE_THREADED OR WOLFSSL_LINUX_KM) + message(FATAL_ERROR + "WOLFSSL_RNG_ATFORK requires threads and no kernel module.") + endif() + if(NOT WOLFSSL_RNG OR NOT WOLFSSL_RNG_LOCK OR NOT WOLFSSL_HASH_DRBG OR + WOLFSSL_RNG_BANK OR WOLFSSL_STATICMEMORY) + message(FATAL_ERROR + "WOLFSSL_RNG_ATFORK requires WOLFSSL_RNG, WOLFSSL_RNG_LOCK and WOLFSSL_HASH_DRBG, and no WOLFSSL_RNG_BANK or WOLFSSL_STATICMEMORY.") + endif() + set(RNG_ATFORK_SAVED_LIBS "${CMAKE_REQUIRED_LIBRARIES}") + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + check_function_exists("pthread_atfork" HAVE_PTHREAD_ATFORK) + set(CMAKE_REQUIRED_LIBRARIES "${RNG_ATFORK_SAVED_LIBS}") + if(NOT HAVE_PTHREAD_ATFORK) + message(FATAL_ERROR "WOLFSSL_RNG_ATFORK requires pthread_atfork.") + endif() + list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_ATFORK") +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 b2ff85b3c7d..f3fee45fd75 100644 --- a/configure.ac +++ b/configure.ac @@ -2754,6 +2754,46 @@ 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 +# raw enable_rng_lock: the option defaults to yes, so only an explicit ask +# is an error here +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) +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: disabled)])], + [ ENABLED_RNG_ATFORK=$enableval ], + [ ENABLED_RNG_ATFORK=no ] + ) + +case "$ENABLED_RNG_ATFORK" in + yes|no) ;; + *) AC_MSG_ERROR([--enable-rng-atfork takes yes or no]) ;; +esac +if test "$ENABLED_RNG_ATFORK" = "yes" && \ + { test "$ENABLED_RNG" = "no" || test "$ENABLED_RNG_LOCK" = "no"; } +then + AC_MSG_ERROR([--enable-rng-atfork requires --enable-rng and --enable-rng-lock]) +fi + + # DTLS-SCTP AC_ARG_ENABLE([sctp], [AS_HELP_STRING([--enable-sctp],[Enable wolfSSL DTLS-SCTP support (default: disabled)])], @@ -13248,6 +13288,49 @@ then AM_CFLAGS="$AM_CFLAGS -DHAVE___UINT128_T=1" fi +# RNG lock and fork handlers need threads; the handlers need pthread_atfork +# and everything the WC_RNG_LOCK_ATFORK gate in random.h asks for +# (raw enable_rng_lock: only an explicit ask is an error, the default is yes) +case "$AM_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 +if test "$ENABLED_RNG_ATFORK" = "yes" +then + if test "$ENABLED_RNG_LOCK_THREADS" = "no" || \ + test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" + then + AC_MSG_ERROR([--enable-rng-atfork requires threads and no kernel module]) + fi + case "$AM_CFLAGS" in + *-DWOLFSSL_STATIC_MEMORY*) ENABLED_RNG_ATFORK_HEAP=no ;; + *) ENABLED_RNG_ATFORK_HEAP=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_HEAP" = "no" || test "$ENABLED_WNR" = "yes" || \ + { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } + then + AC_MSG_ERROR([--enable-rng-atfork requires the Hash DRBG and none of selftest, FIPS before v7, entropy-memuse, rng-bank, static memory or netRandom]) + fi + saved_LIBS="$LIBS" + AC_SEARCH_LIBS([pthread_atfork], [pthread]) + LIBS="$saved_LIBS" + if test "$ac_cv_search_pthread_atfork" = "no" + then + AC_MSG_ERROR([--enable-rng-atfork requires pthread_atfork]) + fi + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_ATFORK" +fi + # Add HAVE_GETPID to AM_CFLAGS for inclusion in options.h if test "$ac_cv_func_getpid" = "yes" then diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index b07b2a2e6c3..1da6b755350 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -56,8 +56,38 @@ 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's lock. Define WC_RNG_NO_LOCK (configure --disable-rng-lock) + to leave the lock out of a build that never shares one; it needs a heap. + The lock covers the Hash DRBG; a crypto callback, RDRAND, a hardware TRNG + or an async marker answers a generate before it is taken. A WC_RNG_SEED_CB + callback and any hash crypto callback reached during a generate run with + the lock held, so they must not use the RNG API. Initialize only a WC_RNG + that is new or has been freed, and let no other thread use the instance + across wc_InitRng*() or wc_FreeRng(), which do not lock. Without the + handlers below, an instance whose lock another thread held at fork() + stays locked in the child. + + POSIX lets the child of a multithreaded process only exec. Define + WC_RNG_ATFORK (configure --enable-rng-atfork) to install pthread_atfork() + handlers instead; wolfCrypt_Init() registers them and must run before any + thread or fork, and wc_InitRng*() returns BAD_STATE_E before it. They + hold every instance lock across fork(), which then + waits for any generate or reseed in flight, and start the locks over in + the child, which reseeds before its next output. The handlers cover only + WC_RNG locks, not other wolfSSL mutexes. + They do not cover clone(), vfork() or _Fork(). A fork() from inside a + WC_RNG_SEED_CB seed callback or a hash crypto callback reached during a + generate, which run with the instance lock held, deadlocks. The child's + first generate reseeds and allocates, so the allocator must be fork safe, + as glibc and musl are; the handlers are refused with entropy-memuse, the + RNG bank and static memory, whose locks they do not repair. They stay + registered for the life of the process, past wolfCrypt_Cleanup(), so a + fork() after the library was unloaded with dlclose() calls unmapped code. + \return 0 on success. \return MEMORY_E XMALLOC failed + \return BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() \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 +96,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 +140,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 +181,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 +216,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 +352,9 @@ 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 + \return BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() \param rng Pointer to store WC_RNG pointer \param nonce Nonce buffer (can be NULL) @@ -359,6 +397,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 BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() \param rng WC_RNG to initialize \param heap Heap hint (can be NULL) @@ -382,6 +423,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 BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() \param rng WC_RNG to initialize \param nonce Nonce buffer @@ -406,6 +450,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 BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() \param rng WC_RNG to initialize \param nonce Nonce buffer @@ -453,6 +500,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..c5952d23392 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -436,6 +436,14 @@ 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 */ + /* needs pthreads and the lock above, so not with WC_RNG_NO_LOCK */ + #define WC_RNG_ATFORK +#endif + /* ------------------------------------------------------------------------- */ /* Custom Standard Lib */ diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 2e396516740..98cebc0069e 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: off + * child can keep using its WC_RNG * 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 @@ -485,6 +489,185 @@ static int UnlockDrbgState(void) #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ +#ifdef WC_RNG_HAVE_LOCK +#ifdef WC_RNG_LOCK_ATFORK +/* Every live lock, under drbgStateMutex. */ +static WC_RNG_LOCK* rngList = NULL; +static int rngAtForkSet = 0; +/* Set in prepare, read in parent, by the forking thread only. */ +static THREAD_LS_T int rngForkLocked = 0; + +/* Before fork(): the forking thread takes every lock. */ +static void RngAtForkPrepare(void) +{ + WC_RNG_LOCK* n; + rngForkLocked = (LockDrbgState() == 0); + if (!rngForkLocked) + return; /* the list cannot be walked safely */ + for (n = rngList; n != NULL; n = n->next) { + if (!n->broken) + (void)wc_LockMutex(&n->mutex); + } +} + +/* 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)wc_UnLockMutex(&n->mutex); + } + (void)UnlockDrbgState(); +} + +/* Child after fork(): new locks, and every DRBG reseeds before its next + * output. If prepare could not lock the list nothing was held, so every + * instance is marked broken for good and fails closed. */ +static void RngAtForkChild(void) +{ + WC_RNG_LOCK* n; + int stateOk; + for (n = rngList; n != NULL; n = n->next) { + XMEMSET(&n->mutex, 0, sizeof(n->mutex)); + n->noMutex = (wc_InitMutex(&n->mutex) != 0); + n->broken = n->broken || n->noMutex || !rngForkLocked; + #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 + } + XMEMSET(&drbgStateMutex, 0, sizeof(drbgStateMutex)); + stateOk = (wc_InitMutex(&drbgStateMutex) == 0); +#ifndef WOLFSSL_MUTEX_INITIALIZER + drbgStateMutex_inited = stateOk ? WC_DRBG_MUTEX_INITED + : WC_DRBG_MUTEX_UNINITED; +#endif + if (!stateOk) { + for (n = rngList; n != NULL; n = n->next) + n->broken = 1; + } +} + +/* From wolfCrypt_Init, before any thread or fork: registers the handlers. */ +int wc_RngAtForkInit(void) +{ + if (rngAtForkSet) + return 0; + if (pthread_atfork(RngAtForkPrepare, RngAtForkParent, + RngAtForkChild) != 0) + return MEMORY_E; + rngAtForkSet = 1; + return 0; +} + +static int RngRegister(WC_RNG_LOCK* n) +{ + int ret; + if (!rngAtForkSet) + return BAD_STATE_E; /* wolfCrypt_Init has not run */ + ret = LockDrbgState(); + if (ret != 0) + return ret; + n->next = rngList; + n->prev = &rngList; + if (rngList != NULL) + rngList->prev = &n->next; + rngList = n; + (void)UnlockDrbgState(); + return 0; +} + +static void RngUnregister(WC_RNG_LOCK* n) +{ + /* Unlink even without the state mutex: the DRBG is freed next, and a + * stale node would point at it at the next fork. */ + int locked = (LockDrbgState() == 0); + *n->prev = n->next; + if (n->next != NULL) + n->next->prev = n->prev; + if (locked) + (void)UnlockDrbgState(); + else + WOLFSSL_MSG("RngUnregister: state mutex unavailable"); +} +#endif /* WC_RNG_LOCK_ATFORK */ + +/* Creates the lock and, with fork handlers, registers it. */ +static int RngLockInit(WC_RNG* rng) +{ + int ret = 0; + 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; + if (wc_InitMutex(&n->mutex) != 0) { + XFREE(n, rng->heap, DYNAMIC_TYPE_RNG); + return BAD_MUTEX_E; + } +#ifdef WC_RNG_LOCK_ATFORK +#ifndef NO_SHA256 + n->drbg = rng->drbg; +#endif +#ifdef WOLFSSL_DRBG_SHA512 + n->drbg512 = rng->drbg512; +#endif + ret = RngRegister(n); + if (ret != 0) { + (void)wc_FreeMutex(&n->mutex); + XFREE(n, rng->heap, DYNAMIC_TYPE_RNG); + return ret; + } +#endif + rng->lock = n; + return ret; +} + +/* 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; +#ifdef WC_RNG_LOCK_ATFORK + RngUnregister(n); + if (!n->noMutex) +#endif + (void)wc_FreeMutex(&n->mutex); + XFREE(n, n->heap, DYNAMIC_TYPE_RNG); + rng->lock = NULL; +} + +static int RngLockEnter(WC_RNG* rng) +{ + if (rng->lock == NULL) + return 0; +#ifdef WC_RNG_LOCK_ATFORK + if (rng->lock->broken) + return BAD_MUTEX_E; +#endif + return wc_LockMutex(&rng->lock->mutex); +} + +static void RngLockExit(WC_RNG* rng) +{ + if (rng->lock != NULL) + (void)wc_UnLockMutex(&rng->lock->mutex); +} +#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); @@ -704,6 +887,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)) { @@ -713,12 +897,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)) { @@ -728,8 +918,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 @@ -2340,6 +2535,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; } @@ -2589,8 +2791,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()) { @@ -2598,6 +2806,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; } } @@ -2645,6 +2854,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 */ @@ -2708,8 +2918,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 4d60dcaa81d..85e6d317d3f 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -555,6 +555,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 2cfc25eecad..f018985bd0d 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -179,6 +179,13 @@ 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 +#endif + /* printf mappings */ #ifndef WOLFSSL_LOG_PRINTF #if defined(FREESCALE_MQX) || defined(FREESCALE_KSDK_MQX) @@ -928,6 +935,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 @@ -2564,6 +2574,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); @@ -26733,6 +26749,22 @@ static wc_test_ret_t random_rng_test(void) return ret; } +/* Freeing a never-initialized, zeroed WC_RNG must stay a no-op. Not on + * Versal, whose wc_FreeRng resets the shared TRNG, nor without a heap, where + * a WC_RNG embeds its DRBG and is too big for this stack. */ +#if !defined(WOLFSSL_XILINX_CRYPT_VERSAL) && !defined(WOLFSSL_NO_MALLOC) +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) @@ -26942,6 +26974,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)); @@ -27158,12 +27194,318 @@ 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(); } #endif /* !HAVE_HASHDRBG || CUSTOM_RAND_GENERATE_BLOCK || HAVE_INTEL_RDRAND */ +#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; the prepare handler must then wait for it. */ +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 hold = { 0, WC_RNG_FORK_HOLD_NS }; + byte held = 1; + + if (a->rng->lock != NULL && wc_LockMutex(&a->rng->lock->mutex) == 0) { + if (write(a->fd, &held, 1) == 1) + (void)nanosleep(&hold, NULL); + (void)wc_UnLockMutex(&a->rng->lock->mutex); + } + 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, and its + * next block must differ from the parent's. Sets the leak flag when the + * holder could not be joined, so the caller leaves what it may touch alone. */ +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 the child too; + * the handler alone covers builds without it. */ + 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 */ + ret = rng_fork_test(rng, &leak); + if (ret == 0) + ret = rng_fork_test(third, &leak3); + if (!leak3) + wc_rng_free(third); + if (ret != 0) + goto out_free; + } + + /* A lock a fork child could not re-create 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; ThreadSanitizer is the real check. */ + 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 f248ae444b0..04fe27bffad 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -38,6 +38,25 @@ #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_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(__APPLE__) || defined(__linux__)) + #define WC_TEST_RNG_FORK +#endif #ifdef HAVE_STACK_SIZE THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args); @@ -251,6 +270,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 d9c5a9289b9..1a48ae8ccff 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -77,6 +77,53 @@ #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. Needs a heap. */ +#if !defined(WC_RNG_NO_LOCK) && !defined(SINGLE_THREADED) && \ + !defined(WC_NO_RNG) && \ + (!defined(WOLFSSL_NO_MALLOC) || defined(WOLFSSL_STATIC_MEMORY)) && \ + !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 + +/* A forked child cannot release a lock another thread held; POSIX only lets + * that child exec. WC_RNG_ATFORK (--enable-rng-atfork) adds pthread_atfork() + * handlers, registered by wolfCrypt_Init() for the life of the process, so + * the child can keep using its WC_RNG. Not with entropy-memuse, the RNG + * bank, static memory or netRandom, whose locks the handlers cannot fix. */ +#if defined(WC_RNG_HAVE_LOCK) && defined(WOLFSSL_PTHREADS) && \ + defined(WC_RNG_ATFORK) && !defined(HAVE_ENTROPY_MEMUSE) && \ + !defined(WC_RNG_BANK_SUPPORT) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(HAVE_WNR) + #define WC_RNG_LOCK_ATFORK +#endif +#if defined(WC_RNG_ATFORK) && !defined(WC_RNG_LOCK_ATFORK) + #error WC_RNG_ATFORK needs the RNG lock (pthreads, Hash DRBG, a heap) \ + and none of HAVE_ENTROPY_MEMUSE, WC_RNG_BANK_SUPPORT, \ + WOLFSSL_STATIC_MEMORY or HAVE_WNR +#endif + +#ifdef WC_RNG_HAVE_LOCK +/* Per instance lock, on the heap so a memset of the WC_RNG cannot break it. */ +typedef struct WC_RNG_LOCK { + wolfSSL_Mutex mutex; + void* heap; +#ifdef WC_RNG_LOCK_ATFORK + 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 */ + int noMutex; /* a fork child could not re-create it */ +#endif +} WC_RNG_LOCK; +#endif +#ifdef WC_RNG_LOCK_ATFORK +WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ +#endif + /* avoid redefinition of structs */ #if !defined(HAVE_FIPS) || \ @@ -426,6 +473,11 @@ struct WC_RNG { #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) int devId; #endif +#ifdef WC_RNG_HAVE_LOCK + /* NULL until wc_InitRng succeeds. Initialize only a new or freed WC_RNG: + * wc_InitRng over a live one leaks this and its fork registry entry. */ + WC_RNG_LOCK* lock; +#endif }; #endif /* NO FIPS or have FIPS v2*/ @@ -450,6 +502,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 898ef9ac21ca09d1bbc8295fccb3dd1b01575ace Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sat, 5 Sep 2026 09:17:17 -0600 Subject: [PATCH 02/15] Keep the RNG lock inline unless fork handlers need it on the heap --- doc/dox_comments/header_files/random.h | 7 +++-- wolfcrypt/src/random.c | 43 +++++++++++++++++++------- wolfcrypt/test/test.h | 2 +- wolfssl/wolfcrypt/random.h | 28 ++++++++--------- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index 1da6b755350..529e284b5ed 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -58,7 +58,7 @@ int wc_FreeNetRandom(void); One WC_RNG may be shared between threads: each generate and reseed holds the instance's lock. Define WC_RNG_NO_LOCK (configure --disable-rng-lock) - to leave the lock out of a build that never shares one; it needs a heap. + to leave the lock out of a build that never shares one. The lock covers the Hash DRBG; a crypto callback, RDRAND, a hardware TRNG or an async marker answers a generate before it is taken. A WC_RNG_SEED_CB callback and any hash crypto callback reached during a generate run with @@ -70,8 +70,9 @@ int wc_FreeNetRandom(void); POSIX lets the child of a multithreaded process only exec. Define WC_RNG_ATFORK (configure --enable-rng-atfork) to install pthread_atfork() - handlers instead; wolfCrypt_Init() registers them and must run before any - thread or fork, and wc_InitRng*() returns BAD_STATE_E before it. They + handlers instead; they need a heap, wolfCrypt_Init() registers them and + must run before any thread or fork, and wc_InitRng*() returns BAD_STATE_E + before it. They hold every instance lock across fork(), which then waits for any generate or reseed in flight, and start the locks over in the child, which reseeds before its next output. The handlers cover only diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 98cebc0069e..fd8397afbbb 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -489,7 +489,6 @@ static int UnlockDrbgState(void) #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ -#ifdef WC_RNG_HAVE_LOCK #ifdef WC_RNG_LOCK_ATFORK /* Every live lock, under drbgStateMutex. */ static WC_RNG_LOCK* rngList = NULL; @@ -598,12 +597,11 @@ static void RngUnregister(WC_RNG_LOCK* n) else WOLFSSL_MSG("RngUnregister: state mutex unavailable"); } -#endif /* WC_RNG_LOCK_ATFORK */ -/* Creates the lock and, with fork handlers, registers it. */ +/* Creates the lock and registers it. */ static int RngLockInit(WC_RNG* rng) { - int ret = 0; + int ret; WC_RNG_LOCK* n = (WC_RNG_LOCK*)XMALLOC(sizeof(*n), rng->heap, DYNAMIC_TYPE_RNG); if (n == NULL) @@ -614,7 +612,6 @@ static int RngLockInit(WC_RNG* rng) XFREE(n, rng->heap, DYNAMIC_TYPE_RNG); return BAD_MUTEX_E; } -#ifdef WC_RNG_LOCK_ATFORK #ifndef NO_SHA256 n->drbg = rng->drbg; #endif @@ -627,9 +624,8 @@ static int RngLockInit(WC_RNG* rng) XFREE(n, rng->heap, DYNAMIC_TYPE_RNG); return ret; } -#endif rng->lock = n; - return ret; + return 0; } /* Safe on a zeroed WC_RNG that never got a lock. */ @@ -638,10 +634,8 @@ static void RngLockFree(WC_RNG* rng) WC_RNG_LOCK* n = rng->lock; if (n == NULL) return; -#ifdef WC_RNG_LOCK_ATFORK RngUnregister(n); if (!n->noMutex) -#endif (void)wc_FreeMutex(&n->mutex); XFREE(n, n->heap, DYNAMIC_TYPE_RNG); rng->lock = NULL; @@ -651,10 +645,8 @@ static int RngLockEnter(WC_RNG* rng) { if (rng->lock == NULL) return 0; -#ifdef WC_RNG_LOCK_ATFORK if (rng->lock->broken) return BAD_MUTEX_E; -#endif return wc_LockMutex(&rng->lock->mutex); } @@ -663,6 +655,35 @@ static void RngLockExit(WC_RNG* rng) if (rng->lock != NULL) (void)wc_UnLockMutex(&rng->lock->mutex); } +#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) +{ + return rng->lockInited ? wc_LockMutex(&rng->lock) : 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 diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index 04fe27bffad..86b91733d6a 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -47,7 +47,7 @@ #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_STATIC_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ (defined(WOLFSSL_PTHREADS) || \ (defined(USE_WINDOWS_API) && !defined(_WIN32_WCE))) #define WC_TEST_RNG_LOCK diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 1a48ae8ccff..981b1686f57 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -78,10 +78,9 @@ #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. Needs a heap. */ + * kernel modules have their own lock-free design. */ #if !defined(WC_RNG_NO_LOCK) && !defined(SINGLE_THREADED) && \ !defined(WC_NO_RNG) && \ - (!defined(WOLFSSL_NO_MALLOC) || defined(WOLFSSL_STATIC_MEMORY)) && \ !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)) @@ -91,12 +90,13 @@ /* A forked child cannot release a lock another thread held; POSIX only lets * that child exec. WC_RNG_ATFORK (--enable-rng-atfork) adds pthread_atfork() * handlers, registered by wolfCrypt_Init() for the life of the process, so - * the child can keep using its WC_RNG. Not with entropy-memuse, the RNG - * bank, static memory or netRandom, whose locks the handlers cannot fix. */ + * the child can keep using its WC_RNG. Needs a heap. Not with + * entropy-memuse, the RNG bank, static memory or netRandom, whose locks the + * handlers cannot fix. */ #if defined(WC_RNG_HAVE_LOCK) && defined(WOLFSSL_PTHREADS) && \ - defined(WC_RNG_ATFORK) && !defined(HAVE_ENTROPY_MEMUSE) && \ - !defined(WC_RNG_BANK_SUPPORT) && !defined(WOLFSSL_STATIC_MEMORY) && \ - !defined(HAVE_WNR) + defined(WC_RNG_ATFORK) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(HAVE_ENTROPY_MEMUSE) && !defined(WC_RNG_BANK_SUPPORT) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(HAVE_WNR) #define WC_RNG_LOCK_ATFORK #endif #if defined(WC_RNG_ATFORK) && !defined(WC_RNG_LOCK_ATFORK) @@ -105,22 +105,19 @@ WOLFSSL_STATIC_MEMORY or HAVE_WNR #endif -#ifdef WC_RNG_HAVE_LOCK -/* Per instance lock, on the heap so a memset of the WC_RNG cannot break it. */ +#ifdef WC_RNG_LOCK_ATFORK +/* With fork handlers the lock lives on the heap, so a memset of the WC_RNG + * cannot break it or the fork registry. */ typedef struct WC_RNG_LOCK { wolfSSL_Mutex mutex; void* heap; -#ifdef WC_RNG_LOCK_ATFORK 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 */ int noMutex; /* a fork child could not re-create it */ -#endif } WC_RNG_LOCK; -#endif -#ifdef WC_RNG_LOCK_ATFORK WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ #endif @@ -473,10 +470,13 @@ struct WC_RNG { #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) int devId; #endif -#ifdef WC_RNG_HAVE_LOCK +#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 its fork registry entry. */ WC_RNG_LOCK* lock; +#elif defined(WC_RNG_HAVE_LOCK) + wolfSSL_Mutex lock; /* serializes generate and reseed */ + byte lockInited; /* nonzero once lock exists */ #endif }; From 884f85fccc0310afd074650270073b9fc5f2b121 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sat, 5 Sep 2026 16:22:11 -0600 Subject: [PATCH 03/15] Turn the RNG fork handlers on wherever pthread_atfork exists and pin the library against dlclose --- .github/configs/os-check-linux.json | 12 ++-- .wolfssl_known_macro_extras | 2 + CMakeLists.txt | 45 +++++++++------ cmake/options.h.in | 2 + configure.ac | 67 +++++++++++++++++------ doc/dox_comments/header_files/random.h | 47 ++++++++-------- examples/configs/user_settings_template.h | 5 +- wolfcrypt/src/random.c | 43 ++++++++++----- wolfcrypt/src/wc_port.c | 18 ++++++ wolfcrypt/test/test.h | 1 + wolfssl/wolfcrypt/random.h | 19 +++---- 11 files changed, 170 insertions(+), 91 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 963710152e5..de278e83038 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -287,12 +287,12 @@ "configure": ["--enable-she=standard", "--enable-cmac"]}, {"name": "no-verify-oid-fpki", "minutes": 1.2, "configure": ["CPPFLAGS=-DNO_VERIFY_OID -DWOLFSSL_FPKI"]}, -{"name": "rng-atfork", "minutes": 1.2, - "comment": "pthread_atfork handlers for a forked child that keeps using its WC_RNG; runs the fork half of the RNG thread test.", - "configure": ["--enable-rng-atfork"]}, -{"name": "rng-atfork-no-getpid", "minutes": 1.2, - "comment": "Same handlers without the pid check, so the child handler alone must make the forked child reseed.", - "configure": ["--enable-rng-atfork", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, +{"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": "rng-no-getpid", "minutes": 1.2, + "comment": "Default RNG fork handlers without the pid check, so the child handler alone must make the forked child reseed.", + "configure": ["CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, {"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.", diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 6ab44d93c9c..703328a2006 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -606,6 +606,8 @@ RNG_CR_CONDRST RNG_SR_BUSY RTC_ALARMSUBSECONDMASK_ALL RTE_CMSIS_RTOS_RTX +RTLD_NODELETE +RTLD_NOLOAD RTOS_MODULE_NET_AVAIL RTPLATFORM SAES diff --git a/CMakeLists.txt b/CMakeLists.txt index a7b8508a523..3248bf050f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4003,29 +4003,38 @@ if(NOT WOLFSSL_STATICMEMORY STREQUAL "no") endif() endif() -# RNG fork handlers (a forked child keeps using its WC_RNG). Below the -# static memory option because it reads it. +# RNG fork handlers (a forked child keeps using its WC_RNG). On by default; +# a build that cannot carry them drops them. Below the static memory option +# because it reads it. add_option("WOLFSSL_RNG_ATFORK" - "Enable pthread_atfork handlers so a forked child can keep using a WC_RNG (default: disabled)" - "no" "yes;no") + "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_SINGLE_THREADED OR WOLFSSL_LINUX_KM) - message(FATAL_ERROR - "WOLFSSL_RNG_ATFORK requires threads and no kernel module.") - endif() - if(NOT WOLFSSL_RNG OR NOT WOLFSSL_RNG_LOCK OR NOT WOLFSSL_HASH_DRBG OR - WOLFSSL_RNG_BANK OR WOLFSSL_STATICMEMORY) - message(FATAL_ERROR - "WOLFSSL_RNG_ATFORK requires WOLFSSL_RNG, WOLFSSL_RNG_LOCK and WOLFSSL_HASH_DRBG, and no WOLFSSL_RNG_BANK or WOLFSSL_STATICMEMORY.") + 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() + set(RNG_ATFORK_SAVED_LIBS "${CMAKE_REQUIRED_LIBRARIES}") + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + check_function_exists("pthread_atfork" HAVE_PTHREAD_ATFORK) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) + check_function_exists("dladdr" HAVE_DLADDR) + set(CMAKE_REQUIRED_LIBRARIES "${RNG_ATFORK_SAVED_LIBS}") + if(NOT HAVE_PTHREAD_ATFORK) + set(RNG_ATFORK_NEEDS "pthread_atfork") + elseif(NOT HAVE_DLADDR) + set(RNG_ATFORK_NEEDS "dladdr to pin the library against dlclose") + endif() endif() - set(RNG_ATFORK_SAVED_LIBS "${CMAKE_REQUIRED_LIBRARIES}") - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) - check_function_exists("pthread_atfork" HAVE_PTHREAD_ATFORK) - set(CMAKE_REQUIRED_LIBRARIES "${RNG_ATFORK_SAVED_LIBS}") - if(NOT HAVE_PTHREAD_ATFORK) - message(FATAL_ERROR "WOLFSSL_RNG_ATFORK requires pthread_atfork.") + if(RNG_ATFORK_NEEDS) + message(STATUS "RNG fork handlers off: they need ${RNG_ATFORK_NEEDS}") + else() + list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_ATFORK" "-DWC_RNG_ATFORK_PIN") + list(APPEND WOLFSSL_LINK_LIBS ${CMAKE_DL_LIBS}) endif() - list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_ATFORK") endif() # TLS (enabled by default; disable for wolfCrypt-only) diff --git a/cmake/options.h.in b/cmake/options.h.in index f4bd1681162..976dcf795dc 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 WC_RNG_ATFORK_PIN +#cmakedefine WC_RNG_ATFORK_PIN #undef HAVE_VALGRIND #cmakedefine HAVE_VALGRIND #undef HAVE_CRL_MONITOR diff --git a/configure.ac b/configure.ac index f3fee45fd75..d0856aa2eb2 100644 --- a/configure.ac +++ b/configure.ac @@ -2776,21 +2776,25 @@ then AM_CFLAGS="$AM_CFLAGS -DWC_RNG_NO_LOCK" fi -# RNG fork handlers (a forked child keeps using its WC_RNG) +# RNG fork handlers (a forked child keeps using its WC_RNG); on wherever the +# build supports them, decided in the late check with the other RNG checks 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: disabled)])], + [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=no ] + [ 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_ATFORK" = "yes" && \ - { test "$ENABLED_RNG" = "no" || test "$ENABLED_RNG_LOCK" = "no"; } +if test "$ENABLED_RNG" = "no" || test "$ENABLED_RNG_LOCK" = "no" then - AC_MSG_ERROR([--enable-rng-atfork requires --enable-rng and --enable-rng-lock]) + 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 @@ -13291,7 +13295,7 @@ fi # RNG lock and fork handlers need threads; the handlers need pthread_atfork # and everything the WC_RNG_LOCK_ATFORK gate in random.h asks for # (raw enable_rng_lock: only an explicit ask is an error, the default is yes) -case "$AM_CFLAGS" in +case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in *-DSINGLE_THREADED*) ENABLED_RNG_LOCK_THREADS=no ;; *) ENABLED_RNG_LOCK_THREADS=yes ;; esac @@ -13303,15 +13307,19 @@ if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG_LOCK_THREADS" = "no" then AC_MSG_ERROR([--enable-rng-lock requires threads]) fi +# The handlers default to on; a build that cannot carry them drops them +# silently, unless they were asked for explicitly if test "$ENABLED_RNG_ATFORK" = "yes" then + RNG_ATFORK_NEEDS="" if test "$ENABLED_RNG_LOCK_THREADS" = "no" || \ test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" then - AC_MSG_ERROR([--enable-rng-atfork requires threads and no kernel module]) + RNG_ATFORK_NEEDS="threads and no kernel module" fi - case "$AM_CFLAGS" in - *-DWOLFSSL_STATIC_MEMORY*) ENABLED_RNG_ATFORK_HEAP=no ;; + case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in + *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*) + ENABLED_RNG_ATFORK_HEAP=no ;; *) ENABLED_RNG_ATFORK_HEAP=yes ;; esac if test "$ENABLED_SELFTEST" = "yes" || test "x$ENABLED_HASHDRBG" != "xyes" || \ @@ -13319,16 +13327,41 @@ then test "$ENABLED_RNG_ATFORK_HEAP" = "no" || test "$ENABLED_WNR" = "yes" || \ { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } then - AC_MSG_ERROR([--enable-rng-atfork requires the Hash DRBG and none of selftest, FIPS before v7, entropy-memuse, rng-bank, static memory or netRandom]) + 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 or netRandom" fi - saved_LIBS="$LIBS" - AC_SEARCH_LIBS([pthread_atfork], [pthread]) - LIBS="$saved_LIBS" - if test "$ac_cv_search_pthread_atfork" = "no" + 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 + # the handlers can never be removed, so the image holding them is + # pinned with dladdr and dlopen; without them the handlers stay out + AC_SEARCH_LIBS([dladdr], [dl]) + if test "$ac_cv_search_dladdr" = "no" + then + RNG_ATFORK_NEEDS="dladdr to pin the library against dlclose" + fi + fi + if test -n "$RNG_ATFORK_NEEDS" then - AC_MSG_ERROR([--enable-rng-atfork requires pthread_atfork]) + 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 - AM_CFLAGS="$AM_CFLAGS -DWC_RNG_ATFORK" +fi +if test "$ENABLED_RNG_ATFORK" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_ATFORK -DWC_RNG_ATFORK_PIN" fi # Add HAVE_GETPID to AM_CFLAGS for inclusion in options.h diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index 529e284b5ed..a56c2dfa9c4 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -65,30 +65,30 @@ int wc_FreeNetRandom(void); the lock held, so they must not use the RNG API. Initialize only a WC_RNG that is new or has been freed, and let no other thread use the instance across wc_InitRng*() or wc_FreeRng(), which do not lock. Without the - handlers below, an instance whose lock another thread held at fork() + fork handlers below, an instance whose lock another thread held at fork() stays locked in the child. - POSIX lets the child of a multithreaded process only exec. Define - WC_RNG_ATFORK (configure --enable-rng-atfork) to install pthread_atfork() - handlers instead; they need a heap, wolfCrypt_Init() registers them and - must run before any thread or fork, and wc_InitRng*() returns BAD_STATE_E - before it. They - hold every instance lock across fork(), which then - waits for any generate or reseed in flight, and start the locks over in - the child, which reseeds before its next output. The handlers cover only - WC_RNG locks, not other wolfSSL mutexes. - They do not cover clone(), vfork() or _Fork(). A fork() from inside a - WC_RNG_SEED_CB seed callback or a hash crypto callback reached during a - generate, which run with the instance lock held, deadlocks. The child's - first generate reseeds and allocates, so the allocator must be fork safe, - as glibc and musl are; the handlers are refused with entropy-memuse, the - RNG bank and static memory, whose locks they do not repair. They stay - registered for the life of the process, past wolfCrypt_Cleanup(), so a - fork() after the library was unloaded with dlclose() calls unmapped code. + POSIX lets the child of a multithreaded process only exec. Where + configure or CMake find pthread_atfork(), fork handlers let it keep using + its WC_RNG instead: they hold every instance lock across fork(), which + then waits for any generate or reseed in flight, and start the locks over + in the child, which reseeds before its next output. Configure with + --disable-rng-atfork to leave them out; a user_settings build defines + WC_RNG_ATFORK to turn them on. wolfCrypt_Init() registers them, or the + first wc_InitRng() does. They can never be unregistered, so where dladdr() + exists the library pins itself against dlclose(). They cover only WC_RNG + locks, not other wolfSSL mutexes. They do not cover clone(), vfork() or + _Fork(). A fork() from inside a WC_RNG_SEED_CB seed callback or a hash + crypto callback reached during a generate, which run with the instance + lock held, deadlocks. The child's first generate reseeds and allocates, + so the allocator must be fork safe, as glibc and musl are. Some builds + cannot have the handlers, and there a forked child may only exec: builds + without pthreads, the Hash DRBG, a heap or dladdr(), and builds with + entropy-memuse, the RNG bank, static memory or netRandom, whose locks the + handlers cannot repair. \return 0 on success. - \return MEMORY_E XMALLOC failed - \return BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() + \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 @@ -355,7 +355,6 @@ WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap); \return MEMORY_E Memory allocation failed \return BAD_MUTEX_E the lock that lets threads share this rng could not be created - \return BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() \param rng Pointer to store WC_RNG pointer \param nonce Nonce buffer (can be NULL) @@ -400,7 +399,7 @@ void wc_rng_free(WC_RNG* rng); \return RNG_FAILURE_E Initialization failed \return BAD_MUTEX_E the lock that lets threads share this rng could not be created - \return BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() + \return MEMORY_E the fork handlers could not be registered \param rng WC_RNG to initialize \param heap Heap hint (can be NULL) @@ -426,7 +425,7 @@ int wc_InitRng_ex(WC_RNG* rng, void* heap, int devId); \return RNG_FAILURE_E Initialization failed \return BAD_MUTEX_E the lock that lets threads share this rng could not be created - \return BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() + \return MEMORY_E the fork handlers could not be registered \param rng WC_RNG to initialize \param nonce Nonce buffer @@ -453,7 +452,7 @@ int wc_InitRngNonce(WC_RNG* rng, byte* nonce, word32 nonceSz); \return RNG_FAILURE_E Initialization failed \return BAD_MUTEX_E the lock that lets threads share this rng could not be created - \return BAD_STATE_E WC_RNG_ATFORK build before wolfCrypt_Init() + \return MEMORY_E the fork handlers could not be registered \param rng WC_RNG to initialize \param nonce Nonce buffer diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index c5952d23392..1e9e7251456 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -440,8 +440,11 @@ extern "C" { #define WC_RNG_NO_LOCK #endif #if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ - /* needs pthreads and the lock above, so not with WC_RNG_NO_LOCK */ + /* configure probes for these; here they are asserted. Needs pthreads, + * a heap and the lock above (not WC_RNG_NO_LOCK); the pin needs dladdr + * and keeps the library mapped, since the handlers cannot be removed */ #define WC_RNG_ATFORK + #define WC_RNG_ATFORK_PIN #endif diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index fd8397afbbb..6e7a45aa2ed 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -39,8 +39,8 @@ This library contains implementation for the random number generator. * 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: off - * child can keep using its WC_RNG + * 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 @@ -493,8 +493,8 @@ static int UnlockDrbgState(void) /* Every live lock, under drbgStateMutex. */ static WC_RNG_LOCK* rngList = NULL; static int rngAtForkSet = 0; -/* Set in prepare, read in parent, by the forking thread only. */ -static THREAD_LS_T int rngForkLocked = 0; +/* Set in prepare, read in parent; drbgStateMutex serializes forks. */ +static int rngForkLocked = 0; /* Before fork(): the forking thread takes every lock. */ static void RngAtForkPrepare(void) @@ -555,23 +555,36 @@ static void RngAtForkChild(void) } } -/* From wolfCrypt_Init, before any thread or fork: registers the handlers. */ +/* Registers the handlers once, from wolfCrypt_Init or the first wc_InitRng; + * the flag is decided under drbgStateMutex, the pin taken outside it. */ int wc_RngAtForkInit(void) { - if (rngAtForkSet) - return 0; - if (pthread_atfork(RngAtForkPrepare, RngAtForkParent, - RngAtForkChild) != 0) - return MEMORY_E; - rngAtForkSet = 1; - return 0; + int ret = LockDrbgState(); + int pin = 0; + if (ret != 0) + return ret; + if (!rngAtForkSet) { + if (pthread_atfork(RngAtForkPrepare, RngAtForkParent, + RngAtForkChild) != 0) + ret = MEMORY_E; + else + rngAtForkSet = pin = 1; + } + (void)UnlockDrbgState(); +#ifdef WC_RNG_ATFORK_PIN + if (pin) + wc_RngPinImage((void*)(wc_ptr_t)RngAtForkPrepare); +#else + (void)pin; +#endif + return ret; } static int RngRegister(WC_RNG_LOCK* n) { - int ret; - if (!rngAtForkSet) - return BAD_STATE_E; /* wolfCrypt_Init has not run */ + int ret = wc_RngAtForkInit(); + if (ret != 0) + return ret; ret = LockDrbgState(); if (ret != 0) return ret; diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 85e6d317d3f..6e6b7467e80 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -117,6 +117,9 @@ Threading/Mutex options: #endif #if defined(HAVE_HASHDRBG) && !defined(WC_NO_RNG) #include + #if defined(WC_RNG_LOCK_ATFORK) && defined(WC_RNG_ATFORK_PIN) + #include + #endif #endif #ifdef FREESCALE_LTC_TFM @@ -415,6 +418,21 @@ static WC_DECLARE_INIT_STATE(wolfcrypt_init_state); int aarch64_use_sb = 0; #endif +#if defined(WC_RNG_LOCK_ATFORK) && defined(WC_RNG_ATFORK_PIN) +/* The fork handlers can never be unregistered, so the image that holds them + * must stay mapped: pin it against dlclose() where the loader allows. */ +WOLFSSL_LOCAL void wc_RngPinImage(void* fn) +{ +#if defined(RTLD_NOLOAD) && defined(RTLD_NODELETE) + Dl_info info; + if (dladdr(fn, &info) != 0 && info.dli_fname != NULL) + (void)dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); +#else + (void)fn; +#endif +} +#endif + /* Used to initialize state for wolfcrypt return 0 on success */ diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index 86b91733d6a..7062d8cbbfa 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -48,6 +48,7 @@ !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 diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 981b1686f57..973174e3e92 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -88,22 +88,18 @@ #endif /* A forked child cannot release a lock another thread held; POSIX only lets - * that child exec. WC_RNG_ATFORK (--enable-rng-atfork) adds pthread_atfork() - * handlers, registered by wolfCrypt_Init() for the life of the process, so - * the child can keep using its WC_RNG. Needs a heap. Not with - * entropy-memuse, the RNG bank, static memory or netRandom, whose locks the - * handlers cannot fix. */ + * that child exec. WC_RNG_ATFORK adds pthread_atfork() handlers so the child + * can keep using its WC_RNG. configure and CMake set it wherever they find + * pthread_atfork(); --disable-rng-atfork leaves it out, and a user_settings + * build defines it to opt in. Left out, whatever asked for it, without + * pthreads, the Hash DRBG or a heap, and with entropy-memuse, the RNG bank, + * static memory or netRandom, whose locks the handlers cannot fix. */ #if defined(WC_RNG_HAVE_LOCK) && defined(WOLFSSL_PTHREADS) && \ defined(WC_RNG_ATFORK) && !defined(WOLFSSL_NO_MALLOC) && \ !defined(HAVE_ENTROPY_MEMUSE) && !defined(WC_RNG_BANK_SUPPORT) && \ !defined(WOLFSSL_STATIC_MEMORY) && !defined(HAVE_WNR) #define WC_RNG_LOCK_ATFORK #endif -#if defined(WC_RNG_ATFORK) && !defined(WC_RNG_LOCK_ATFORK) - #error WC_RNG_ATFORK needs the RNG lock (pthreads, Hash DRBG, a heap) \ - and none of HAVE_ENTROPY_MEMUSE, WC_RNG_BANK_SUPPORT, \ - WOLFSSL_STATIC_MEMORY or HAVE_WNR -#endif #ifdef WC_RNG_LOCK_ATFORK /* With fork handlers the lock lives on the heap, so a memset of the WC_RNG @@ -119,6 +115,9 @@ typedef struct WC_RNG_LOCK { int noMutex; /* a fork child could not re-create it */ } WC_RNG_LOCK; WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ +#ifdef WC_RNG_ATFORK_PIN +WOLFSSL_LOCAL void wc_RngPinImage(void* fn); /* keeps fn's image mapped */ +#endif #endif From 6f97c5be68aa808c88d69a4965c601514210d60f Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sat, 5 Sep 2026 19:46:08 -0600 Subject: [PATCH 04/15] Probe pthread_atfork and dladdr with real link tests in CMake --- CMakeLists.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3248bf050f1..2c2033be83d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4011,18 +4011,27 @@ add_option("WOLFSSL_RNG_ATFORK" "yes" "yes;no") if(WOLFSSL_RNG_ATFORK) set(RNG_ATFORK_NEEDS "") - if(WOLFSSL_SINGLE_THREADED OR WOLFSSL_LINUX_KM) + if(WOLFSSL_USER_SETTINGS) + # CMake sets no defines in that mode; user_settings.h defines + # WC_RNG_ATFORK and WC_RNG_ATFORK_PIN itself + 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}) check_function_exists("dladdr" 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) From c2d7c4f4ce848c7bea5162a9c1d19ada78fb8e9d Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 07:27:06 -0600 Subject: [PATCH 05/15] Include random.h in wc_port.c for every RNG build and probe the dlclose pin by linking it --- CMakeLists.txt | 14 ++++++++++++-- configure.ac | 21 ++++++++++++++++----- examples/configs/user_settings_template.h | 3 ++- wolfcrypt/src/wc_port.c | 11 +++++++---- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c2033be83d..628a6c9072e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4029,13 +4029,23 @@ if(WOLFSSL_RNG_ATFORK) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists("pthread_atfork" HAVE_PTHREAD_ATFORK) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) - check_function_exists("dladdr" HAVE_DLADDR) + include(CheckCSourceCompiles) + check_c_source_compiles(" + #define _GNU_SOURCE 1 + #include + #include + int main(void) { + Dl_info info; + if (dladdr((void*)main, &info) != 0 && info.dli_fname != NULL) + (void)dlopen(info.dli_fname, 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 to pin the library against dlclose") + set(RNG_ATFORK_NEEDS "dladdr and dlopen to pin the library against dlclose") endif() endif() if(RNG_ATFORK_NEEDS) diff --git a/configure.ac b/configure.ac index d0856aa2eb2..f57a6466462 100644 --- a/configure.ac +++ b/configure.ac @@ -13342,12 +13342,23 @@ then if test -z "$RNG_ATFORK_NEEDS" then # the handlers can never be removed, so the image holding them is - # pinned with dladdr and dlopen; without them the handlers stay out + # pinned with dladdr and dlopen; without them the handlers stay out. + # Compile and link the real thing: a bare symbol probe passes where + # the header hides dladdr or lacks the RTLD flags. AC_SEARCH_LIBS([dladdr], [dl]) - if test "$ac_cv_search_dladdr" = "no" - then - RNG_ATFORK_NEEDS="dladdr to pin the library against dlclose" - fi + AC_MSG_CHECKING([whether dladdr and dlopen can pin the library]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #define _GNU_SOURCE 1 + #include + #include + ]], [[ + Dl_info info; + if (dladdr((void*)main, &info) != 0 && info.dli_fname != NULL) + (void)dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + ]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + RNG_ATFORK_NEEDS="dladdr and dlopen to pin the library against dlclose"]) fi if test -n "$RNG_ATFORK_NEEDS" then diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index 1e9e7251456..3075fa27cd5 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -442,7 +442,8 @@ extern "C" { #if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ /* configure probes for these; here they are asserted. Needs pthreads, * a heap and the lock above (not WC_RNG_NO_LOCK); the pin needs dladdr - * and keeps the library mapped, since the handlers cannot be removed */ + * (_GNU_SOURCE on glibc and Cygwin) and keeps the library mapped, since + * the handlers cannot be removed */ #define WC_RNG_ATFORK #define WC_RNG_ATFORK_PIN #endif diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 6e6b7467e80..b6356d63ec1 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -19,9 +19,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -#if (defined(__linux__) || defined(__ANDROID__)) && \ - !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_ZEPHYR) && \ - !defined(_GNU_SOURCE) +#if ((defined(__linux__) || defined(__ANDROID__)) && \ + !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_ZEPHYR) && \ + !defined(_GNU_SOURCE)) || \ + (defined(WC_RNG_ATFORK_PIN) && !defined(_GNU_SOURCE)) + /* the second clause: dladdr and Dl_info hide behind it on glibc and Cygwin */ #define _GNU_SOURCE 1 #elif defined(__FreeBSD__) /* for __FreeBSD_version */ @@ -115,7 +117,8 @@ 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 #if defined(WC_RNG_LOCK_ATFORK) && defined(WC_RNG_ATFORK_PIN) #include From a09300903f49e13ed28d5075ee2664b4d2faa98f Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 07:27:06 -0600 Subject: [PATCH 06/15] Expect a reseeded child in the compat RAND test when the fork handlers are on --- tests/api/test_ossl_rand.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index c05805c1868..1c04a8274d4 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -235,7 +235,8 @@ int test_wolfSSL_RAND_bytes(void) 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)); From 2d17f5f42d623725a6f3065c09a3e17933f19c44 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 09:29:20 -0600 Subject: [PATCH 07/15] Read dli_fname through a pointer, since Cygwin declares it as an array --- CMakeLists.txt | 8 ++++++-- configure.ac | 8 ++++++-- wolfcrypt/src/wc_port.c | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 628a6c9072e..3dd76b0ce95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4036,8 +4036,12 @@ if(WOLFSSL_RNG_ATFORK) #include int main(void) { Dl_info info; - if (dladdr((void*)main, &info) != 0 && info.dli_fname != NULL) - (void)dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + const char* name; + if (dladdr((void*)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}") diff --git a/configure.ac b/configure.ac index f57a6466462..1b1c3d0c74e 100644 --- a/configure.ac +++ b/configure.ac @@ -13353,8 +13353,12 @@ then #include ]], [[ Dl_info info; - if (dladdr((void*)main, &info) != 0 && info.dli_fname != NULL) - (void)dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + const char* name; + if (dladdr((void*)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]) diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index b6356d63ec1..a90594fd143 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -428,8 +428,12 @@ WOLFSSL_LOCAL void wc_RngPinImage(void* fn) { #if defined(RTLD_NOLOAD) && defined(RTLD_NODELETE) Dl_info info; - if (dladdr(fn, &info) != 0 && info.dli_fname != NULL) - (void)dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + const char* name; + if (dladdr(fn, &info) == 0) + return; + name = info.dli_fname; /* a pointer on most libcs, an array on Cygwin */ + if (name != NULL && name[0] != '\0') + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); #else (void)fn; #endif From dc4f7534c5444386070fe3424ad2ee7104581590 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 11:13:54 -0600 Subject: [PATCH 08/15] Keep the FreeBSD sys/param.h include and define _GNU_SOURCE for the pin only where Cygwin needs it --- wolfcrypt/src/wc_port.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index a90594fd143..8c465c548b6 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -19,16 +19,20 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -#if ((defined(__linux__) || defined(__ANDROID__)) && \ - !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_ZEPHYR) && \ - !defined(_GNU_SOURCE)) || \ - (defined(WC_RNG_ATFORK_PIN) && !defined(_GNU_SOURCE)) - /* the second clause: dladdr and Dl_info hide behind it on glibc and Cygwin */ +#if (defined(__linux__) || defined(__ANDROID__)) && \ + !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_ZEPHYR) && \ + !defined(_GNU_SOURCE) #define _GNU_SOURCE 1 #elif defined(__FreeBSD__) /* for __FreeBSD_version */ #include #endif +/* Cygwin hides dladdr and Dl_info behind _GNU_SOURCE too; the RNG fork + * handler pin needs them */ +#if defined(WC_RNG_ATFORK_PIN) && !defined(_GNU_SOURCE) && \ + (defined(__CYGWIN__) || defined(__MSYS__)) + #define _GNU_SOURCE 1 +#endif /* wolfCrypt Porting Build Options: From bf2c04dbc95db76faabe1dca3f5fafdb6b7842d7 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 11:13:55 -0600 Subject: [PATCH 09/15] Check the forked child's RAND_bytes result and exit status in the compat RAND test --- tests/api/test_ossl_rand.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index 1c04a8274d4..bb8b5fc01f5 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -219,12 +219,14 @@ int test_wolfSSL_RAND_bytes(void) 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 { /* Parent process. */ @@ -242,7 +244,8 @@ int test_wolfSSL_RAND_bytes(void) ExpectBufNE(randbuf, childrand, sizeof(randbuf)); #endif close(pipefds[0]); - waitpid(pid, &waitstatus, 0); + ExpectIntEQ(waitpid(pid, &waitstatus, 0), pid); + ExpectTrue(WIFEXITED(waitstatus) && WEXITSTATUS(waitstatus) == 0); } RAND_cleanup(); #endif From 41b3c0b22134762a8200327934f046c450a33f1a Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 12:27:41 -0600 Subject: [PATCH 10/15] Require the pin for the fork handlers, keep them out of user settings and memory zero checking builds, and link dl only with the pin --- .github/configs/os-check-linux.json | 3 +++ .wolfssl_known_macro_extras | 1 + CMakeLists.txt | 3 ++- configure.ac | 20 ++++++++++++++-- doc/dox_comments/header_files/random.h | 28 +++++++++++------------ examples/configs/user_settings_template.h | 4 ++-- wolfcrypt/src/wc_port.c | 7 ++---- wolfssl/wolfcrypt/random.h | 14 +++++++----- 8 files changed, 50 insertions(+), 30 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index de278e83038..a98ea981520 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -293,6 +293,9 @@ {"name": "rng-no-getpid", "minutes": 1.2, "comment": "Default RNG fork handlers without the pid check, so the child handler alone must make the forked child reseed.", "configure": ["CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, +{"name": "rng-atfork-off-no-getpid", "minutes": 1.2, + "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", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, {"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.", diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 703328a2006..fa25f0769b7 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -1291,6 +1291,7 @@ __MICROBLAZE__ __MINGW32__ __MINGW64_VERSION_MAJOR __MINGW64__ +__MSYS__ __MWERKS__ __NT__ __OS2__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dd76b0ce95..3df47b565f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4033,11 +4033,12 @@ if(WOLFSSL_RNG_ATFORK) check_c_source_compiles(" #define _GNU_SOURCE 1 #include + #include #include int main(void) { Dl_info info; const char* name; - if (dladdr((void*)main, &info) != 0) { + 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); diff --git a/configure.ac b/configure.ac index 1b1c3d0c74e..e0215912b82 100644 --- a/configure.ac +++ b/configure.ac @@ -13312,6 +13312,11 @@ fi 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 and WC_RNG_ATFORK_PIN" + fi if test "$ENABLED_RNG_LOCK_THREADS" = "no" || \ test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" then @@ -13322,12 +13327,17 @@ then ENABLED_RNG_ATFORK_HEAP=no ;; *) ENABLED_RNG_ATFORK_HEAP=yes ;; esac + case "$AM_CFLAGS $CPPFLAGS $CFLAGS" in + *-DWOLFSSL_CHECK_MEM_ZERO*) ENABLED_RNG_ATFORK_MEMZERO=yes ;; + *) ENABLED_RNG_ATFORK_MEMZERO=no ;; + 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_HEAP" = "no" || test "$ENABLED_WNR" = "yes" || \ + test "$ENABLED_RNG_ATFORK_MEMZERO" = "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 or netRandom" + 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 @@ -13345,16 +13355,18 @@ then # pinned with dladdr and dlopen; without them the handlers stay out. # Compile and link the real thing: 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 and dlopen can pin the library]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #define _GNU_SOURCE 1 #include + #include #include ]], [[ Dl_info info; const char* name; - if (dladdr((void*)main, &info) != 0) { + 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); @@ -13363,6 +13375,8 @@ then [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) RNG_ATFORK_NEEDS="dladdr and dlopen to pin the library against dlclose"]) + RNG_ATFORK_DL_LIBS="$LIBS" + LIBS="$saved_LIBS" fi if test -n "$RNG_ATFORK_NEEDS" then @@ -13377,6 +13391,7 @@ fi if test "$ENABLED_RNG_ATFORK" = "yes" then AM_CFLAGS="$AM_CFLAGS -DWC_RNG_ATFORK -DWC_RNG_ATFORK_PIN" + LIBS="$RNG_ATFORK_DL_LIBS" # -ldl where dladdr needed it, only with the pin fi # Add HAVE_GETPID to AM_CFLAGS for inclusion in options.h @@ -14157,6 +14172,7 @@ echo " * certext: $ENABLED_CERTEXT" echo " * certgencache: $ENABLED_certgencache" echo " * CHACHA: $ENABLED_CHACHA" echo " * XCHACHA: $ENABLED_XCHACHA" +echo " * RNG fork handlers: $ENABLED_RNG_ATFORK" echo " * Hash DRBG: $ENABLED_HASHDRBG" echo " * SHA-256 Hash DRBG: $ENABLED_SHA256_DRBG" echo " * SHA-512 Hash DRBG: $ENABLED_SHA512_DRBG" diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index a56c2dfa9c4..8381f19baeb 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -68,24 +68,24 @@ int wc_FreeNetRandom(void); fork handlers below, an instance whose lock another thread held at fork() stays locked in the child. - POSIX lets the child of a multithreaded process only exec. Where - configure or CMake find pthread_atfork(), fork handlers let it keep using - its WC_RNG instead: they hold every instance lock across fork(), which - then waits for any generate or reseed in flight, and start the locks over - in the child, which reseeds before its next output. Configure with - --disable-rng-atfork to leave them out; a user_settings build defines - WC_RNG_ATFORK to turn them on. wolfCrypt_Init() registers them, or the + POSIX lets the child of a multithreaded process only exec. Where configure + or CMake find pthread_atfork(), fork handlers let it keep using its WC_RNG + instead: they hold every instance lock across fork(), which then waits for + any generate or reseed in flight, and start the locks over in the child, + which reseeds before its next output. Configure with --disable-rng-atfork + to leave them out; a user_settings build defines WC_RNG_ATFORK and + WC_RNG_ATFORK_PIN to turn them on. wolfCrypt_Init() registers them, or the first wc_InitRng() does. They can never be unregistered, so where dladdr() exists the library pins itself against dlclose(). They cover only WC_RNG locks, not other wolfSSL mutexes. They do not cover clone(), vfork() or _Fork(). A fork() from inside a WC_RNG_SEED_CB seed callback or a hash - crypto callback reached during a generate, which run with the instance - lock held, deadlocks. The child's first generate reseeds and allocates, - so the allocator must be fork safe, as glibc and musl are. Some builds - cannot have the handlers, and there a forked child may only exec: builds - without pthreads, the Hash DRBG, a heap or dladdr(), and builds with - entropy-memuse, the RNG bank, static memory or netRandom, whose locks the - handlers cannot repair. + crypto callback reached during a generate, which run with the instance lock + held, deadlocks. The child's first generate reseeds and allocates, so the + allocator must be fork safe, as glibc and musl are. Some builds cannot + have the handlers, and there a forked child may only exec: builds without + pthreads, the Hash DRBG, a heap or dladdr(), and builds with + entropy-memuse, the RNG bank, static memory, netRandom or memory zero + checking, whose locks the handlers cannot repair. \return 0 on success. \return MEMORY_E XMALLOC or the fork handler registration failed diff --git a/examples/configs/user_settings_template.h b/examples/configs/user_settings_template.h index 3075fa27cd5..0c0a8a99d77 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -442,8 +442,8 @@ extern "C" { #if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ /* configure probes for these; here they are asserted. Needs pthreads, * a heap and the lock above (not WC_RNG_NO_LOCK); the pin needs dladdr - * (_GNU_SOURCE on glibc and Cygwin) and keeps the library mapped, since - * the handlers cannot be removed */ + * and dlopen, -ldl on glibc before 2.34, and keeps the library mapped, + * since the handlers cannot be removed */ #define WC_RNG_ATFORK #define WC_RNG_ATFORK_PIN #endif diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 8c465c548b6..6da1cbaaa6b 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -26,11 +26,8 @@ #elif defined(__FreeBSD__) /* for __FreeBSD_version */ #include -#endif -/* Cygwin hides dladdr and Dl_info behind _GNU_SOURCE too; the RNG fork - * handler pin needs them */ -#if defined(WC_RNG_ATFORK_PIN) && !defined(_GNU_SOURCE) && \ - (defined(__CYGWIN__) || defined(__MSYS__)) +#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 diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 973174e3e92..aab88ba41d7 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -91,13 +91,15 @@ * that child exec. WC_RNG_ATFORK adds pthread_atfork() handlers so the child * can keep using its WC_RNG. configure and CMake set it wherever they find * pthread_atfork(); --disable-rng-atfork leaves it out, and a user_settings - * build defines it to opt in. Left out, whatever asked for it, without - * pthreads, the Hash DRBG or a heap, and with entropy-memuse, the RNG bank, - * static memory or netRandom, whose locks the handlers cannot fix. */ + * build defines it, with WC_RNG_ATFORK_PIN, to opt in. Left out, whatever + * asked for it, without pthreads, the Hash DRBG, a heap or the pin, and with + * entropy-memuse, the RNG bank, static memory, netRandom or memory zero + * checking, whose locks the handlers cannot fix. */ #if defined(WC_RNG_HAVE_LOCK) && defined(WOLFSSL_PTHREADS) && \ - defined(WC_RNG_ATFORK) && !defined(WOLFSSL_NO_MALLOC) && \ - !defined(HAVE_ENTROPY_MEMUSE) && !defined(WC_RNG_BANK_SUPPORT) && \ - !defined(WOLFSSL_STATIC_MEMORY) && !defined(HAVE_WNR) + defined(WC_RNG_ATFORK) && defined(WC_RNG_ATFORK_PIN) && \ + !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 From 0c67dbb35873bd169c4547175029449bae889322 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 12:27:41 -0600 Subject: [PATCH 11/15] Reap the forked child before judging it and keep the zeroed free check where the RNG is small --- tests/api/test_ossl_rand.c | 7 +++++-- wolfcrypt/test/test.c | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index bb8b5fc01f5..c056e5b523d 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -232,6 +232,7 @@ int test_wolfSSL_RAND_bytes(void) /* Parent process. */ byte childrand[8] = {0}; int waitstatus = 0; + int reaped; close(pipefds[1]); ExpectIntEQ(RAND_bytes(randbuf, sizeof(randbuf)), 1); @@ -244,8 +245,10 @@ int test_wolfSSL_RAND_bytes(void) ExpectBufNE(randbuf, childrand, sizeof(randbuf)); #endif close(pipefds[0]); - ExpectIntEQ(waitpid(pid, &waitstatus, 0), pid); - ExpectTrue(WIFEXITED(waitstatus) && WEXITSTATUS(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/test/test.c b/wolfcrypt/test/test.c index f018985bd0d..51887034cc6 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -26750,9 +26750,10 @@ static wc_test_ret_t random_rng_test(void) } /* Freeing a never-initialized, zeroed WC_RNG must stay a no-op. Not on - * Versal, whose wc_FreeRng resets the shared TRNG, nor without a heap, where - * a WC_RNG embeds its DRBG and is too big for this stack. */ -#if !defined(WOLFSSL_XILINX_CRYPT_VERSAL) && !defined(WOLFSSL_NO_MALLOC) + * Versal, whose wc_FreeRng resets the shared TRNG, nor where a WC_RNG embeds + * its DRBG (no heap at all) and is 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; From e6e52e5560c3d7f9f3645517e1d213d45b4c8e10 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 15:22:49 -0600 Subject: [PATCH 12/15] Log a failed image pin, tidy the handler gates, and give the compat RAND test its own includes --- CMakeLists.txt | 1 - configure.ac | 4 ++-- tests/api/test_ossl_rand.c | 10 +++++++--- wolfcrypt/src/random.c | 4 ---- wolfcrypt/src/wc_port.c | 17 +++++++++++------ wolfssl/wolfcrypt/random.h | 2 -- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3df47b565f9..2d743520083 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4029,7 +4029,6 @@ if(WOLFSSL_RNG_ATFORK) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) check_function_exists("pthread_atfork" HAVE_PTHREAD_ATFORK) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) - include(CheckCSourceCompiles) check_c_source_compiles(" #define _GNU_SOURCE 1 #include diff --git a/configure.ac b/configure.ac index e0215912b82..62acd778d0f 100644 --- a/configure.ac +++ b/configure.ac @@ -13320,7 +13320,7 @@ then if test "$ENABLED_RNG_LOCK_THREADS" = "no" || \ test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" then - RNG_ATFORK_NEEDS="threads and no kernel module" + 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*) @@ -14172,10 +14172,10 @@ echo " * certext: $ENABLED_CERTEXT" echo " * certgencache: $ENABLED_certgencache" echo " * CHACHA: $ENABLED_CHACHA" echo " * XCHACHA: $ENABLED_XCHACHA" -echo " * RNG fork handlers: $ENABLED_RNG_ATFORK" 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/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index c056e5b523d..fcc83ad11bb 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -21,7 +21,12 @@ #include -#if defined(__linux__) || defined(__FreeBSD__) +#ifndef WC_NO_RNG + #include /* WC_RNG_LOCK_ATFORK */ +#endif +/* the same guard as the fork test below */ +#if defined(OPENSSL_EXTRA) && defined(HAVE_GETPID) && !defined(__MINGW64__) && \ + !defined(__MINGW32__) #include #include #endif @@ -218,7 +223,6 @@ int test_wolfSSL_RAND_bytes(void) ExpectIntGE(pid, 0); if (pid == 0) { ssize_t n_written = 0; - int ok; /* Child process. */ @@ -226,7 +230,7 @@ int test_wolfSSL_RAND_bytes(void) ok = (RAND_bytes(randbuf, sizeof(randbuf)) == 1); n_written = write(pipefds[1], randbuf, sizeof(randbuf)); close(pipefds[1]); - exit((ok && n_written == sizeof(randbuf)) ? 0 : 1); + _exit((ok && n_written == sizeof(randbuf)) ? 0 : 1); } else { /* Parent process. */ diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 6e7a45aa2ed..01026008c0f 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -571,12 +571,8 @@ int wc_RngAtForkInit(void) rngAtForkSet = pin = 1; } (void)UnlockDrbgState(); -#ifdef WC_RNG_ATFORK_PIN if (pin) wc_RngPinImage((void*)(wc_ptr_t)RngAtForkPrepare); -#else - (void)pin; -#endif return ret; } diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 6da1cbaaa6b..4fa1765862c 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -121,8 +121,8 @@ Threading/Mutex options: #ifndef WC_NO_RNG /* random.h defines HAVE_HASHDRBG itself, so no HAVE_HASHDRBG test here */ #include - #if defined(WC_RNG_LOCK_ATFORK) && defined(WC_RNG_ATFORK_PIN) - #include + #ifdef WC_RNG_LOCK_ATFORK + #include /* the pin, which the handlers require */ #endif #endif @@ -422,7 +422,7 @@ static WC_DECLARE_INIT_STATE(wolfcrypt_init_state); int aarch64_use_sb = 0; #endif -#if defined(WC_RNG_LOCK_ATFORK) && defined(WC_RNG_ATFORK_PIN) +#ifdef WC_RNG_LOCK_ATFORK /* The fork handlers can never be unregistered, so the image that holds them * must stay mapped: pin it against dlclose() where the loader allows. */ WOLFSSL_LOCAL void wc_RngPinImage(void* fn) @@ -430,13 +430,18 @@ WOLFSSL_LOCAL void wc_RngPinImage(void* fn) #if defined(RTLD_NOLOAD) && defined(RTLD_NODELETE) Dl_info info; const char* name; - if (dladdr(fn, &info) == 0) + if (dladdr(fn, &info) == 0) { + WOLFSSL_MSG("RNG fork handlers: dladdr failed, image not pinned"); return; + } name = info.dli_fname; /* a pointer on most libcs, an array on Cygwin */ - if (name != NULL && name[0] != '\0') - (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + if (name == NULL || name[0] == '\0' || + dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY) == NULL) { + WOLFSSL_MSG("RNG fork handlers: dlopen failed, image not pinned"); + } #else (void)fn; + WOLFSSL_MSG("RNG fork handlers: no RTLD_NODELETE, image not pinned"); #endif } #endif diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index aab88ba41d7..827432995aa 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -117,10 +117,8 @@ typedef struct WC_RNG_LOCK { int noMutex; /* a fork child could not re-create it */ } WC_RNG_LOCK; WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ -#ifdef WC_RNG_ATFORK_PIN WOLFSSL_LOCAL void wc_RngPinImage(void* fn); /* keeps fn's image mapped */ #endif -#endif /* avoid redefinition of structs */ From 40347a404c5cd135c131339a37744cdeb1ca9618 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 19:23:10 -0600 Subject: [PATCH 13/15] Pin the image before registering the fork handlers and refuse the pin where the loader cannot honor it --- wolfcrypt/src/random.c | 13 +++++++++---- wolfcrypt/src/wc_port.c | 24 ++++++++---------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 01026008c0f..abab0e99230 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -560,19 +560,24 @@ static void RngAtForkChild(void) int wc_RngAtForkInit(void) { int ret = LockDrbgState(); - int pin = 0; 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) { if (pthread_atfork(RngAtForkPrepare, RngAtForkParent, RngAtForkChild) != 0) ret = MEMORY_E; else - rngAtForkSet = pin = 1; + rngAtForkSet = 1; } (void)UnlockDrbgState(); - if (pin) - wc_RngPinImage((void*)(wc_ptr_t)RngAtForkPrepare); return ret; } diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 4fa1765862c..dc806a1e2ab 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -423,26 +423,18 @@ int aarch64_use_sb = 0; #endif #ifdef WC_RNG_LOCK_ATFORK +#if !defined(RTLD_NOLOAD) || !defined(RTLD_NODELETE) + #error "WC_RNG_ATFORK_PIN needs RTLD_NOLOAD and RTLD_NODELETE" +#endif /* The fork handlers can never be unregistered, so the image that holds them - * must stay mapped: pin it against dlclose() where the loader allows. */ + * is pinned against dlclose() before they are registered. */ WOLFSSL_LOCAL void wc_RngPinImage(void* fn) { -#if defined(RTLD_NOLOAD) && defined(RTLD_NODELETE) Dl_info info; - const char* name; - if (dladdr(fn, &info) == 0) { - WOLFSSL_MSG("RNG fork handlers: dladdr failed, image not pinned"); - return; - } - name = info.dli_fname; /* a pointer on most libcs, an array on Cygwin */ - if (name == NULL || name[0] == '\0' || - dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY) == NULL) { - WOLFSSL_MSG("RNG fork handlers: dlopen failed, image not pinned"); - } -#else - (void)fn; - WOLFSSL_MSG("RNG fork handlers: no RTLD_NODELETE, image not pinned"); -#endif + const char* name; /* a pointer on most libcs, an array on Cygwin */ + /* no handle means no dlopen() reaches this image, so no dlclose() can */ + if (dladdr(fn, &info) != 0 && (name = info.dli_fname) != NULL) + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); } #endif From ce0bb17ff3458ecf6f7bd906ccd11e8ed8d99125 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Sun, 6 Sep 2026 20:21:58 -0600 Subject: [PATCH 14/15] Fail closed when prepare cannot lock an instance, keep drbgStateMutex alive for the handlers, and build the compat fork test in the no-getpid CI configs --- .github/configs/os-check-linux.json | 10 ++++---- wolfcrypt/src/random.c | 36 ++++++++++++++++++++--------- wolfcrypt/src/wc_port.c | 8 ++++--- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index a98ea981520..71dbfbb59f0 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -290,12 +290,12 @@ {"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": "rng-no-getpid", "minutes": 1.2, - "comment": "Default RNG fork handlers without the pid check, so the child handler alone must make the forked child reseed.", - "configure": ["CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, -{"name": "rng-atfork-off-no-getpid", "minutes": 1.2, +{"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", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, + "configure": ["--disable-rng-atfork", "--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, {"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.", diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index abab0e99230..e0d02258ea6 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -451,10 +451,18 @@ int wc_DrbgState_MutexInit(void) return 0; } +#ifdef WC_RNG_LOCK_ATFORK +static int rngAtForkSet = 0; /* handlers registered, never unregistered */ +#endif + int wc_DrbgState_MutexFree(void) { #ifndef SINGLE_THREADED #ifndef WOLFSSL_MUTEX_INITIALIZER +#ifdef WC_RNG_LOCK_ATFORK + if (rngAtForkSet) + return 0; /* the fork handlers still lock it */ +#endif if (drbgStateMutex_inited == WC_DRBG_MUTEX_INITED) { int ret = wc_FreeMutex(&drbgStateMutex); if (ret != 0) { @@ -492,8 +500,7 @@ static int UnlockDrbgState(void) #ifdef WC_RNG_LOCK_ATFORK /* Every live lock, under drbgStateMutex. */ static WC_RNG_LOCK* rngList = NULL; -static int rngAtForkSet = 0; -/* Set in prepare, read in parent; drbgStateMutex serializes forks. */ +/* Set in prepare, read in parent and child; libc runs one fork at a time. */ static int rngForkLocked = 0; /* Before fork(): the forking thread takes every lock. */ @@ -504,8 +511,8 @@ static void RngAtForkPrepare(void) if (!rngForkLocked) return; /* the list cannot be walked safely */ for (n = rngList; n != NULL; n = n->next) { - if (!n->broken) - (void)wc_LockMutex(&n->mutex); + if (!n->broken && wc_LockMutex(&n->mutex) != 0) + n->broken = 1; } } @@ -583,12 +590,17 @@ int wc_RngAtForkInit(void) static int RngRegister(WC_RNG_LOCK* n) { - int ret = wc_RngAtForkInit(); - if (ret != 0) - return ret; - ret = LockDrbgState(); + int ret = LockDrbgState(); if (ret != 0) return ret; + if (!rngAtForkSet) { + (void)UnlockDrbgState(); + ret = wc_RngAtForkInit(); + if (ret == 0) + ret = LockDrbgState(); + if (ret != 0) + return ret; + } n->next = rngList; n->prev = &rngList; if (rngList != NULL) @@ -659,9 +671,9 @@ static int RngLockEnter(WC_RNG* rng) { if (rng->lock == NULL) return 0; - if (rng->lock->broken) + if (rng->lock->broken || wc_LockMutex(&rng->lock->mutex) != 0) return BAD_MUTEX_E; - return wc_LockMutex(&rng->lock->mutex); + return 0; } static void RngLockExit(WC_RNG* rng) @@ -690,7 +702,9 @@ static void RngLockFree(WC_RNG* rng) static int RngLockEnter(WC_RNG* rng) { - return rng->lockInited ? wc_LockMutex(&rng->lock) : 0; + if (rng->lockInited && wc_LockMutex(&rng->lock) != 0) + return BAD_MUTEX_E; + return 0; } static void RngLockExit(WC_RNG* rng) diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index dc806a1e2ab..fb132c08a5e 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -432,9 +432,11 @@ WOLFSSL_LOCAL void wc_RngPinImage(void* fn) { Dl_info info; const char* name; /* a pointer on most libcs, an array on Cygwin */ - /* no handle means no dlopen() reaches this image, so no dlclose() can */ - if (dladdr(fn, &info) != 0 && (name = info.dli_fname) != NULL) - (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + if (dladdr(fn, &info) == 0 || (name = info.dli_fname) == NULL || + dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY) == NULL) { + /* no handle means no dlopen() reaches this image, so no dlclose() can */ + WOLFSSL_MSG("RNG fork handlers: no dlopen handle, nothing to pin"); + } } #endif From 0b231e295dc0fc74259e52cfc7a306050e7a268b Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Mon, 7 Sep 2026 07:25:08 -0600 Subject: [PATCH 15/15] Let the forked child in the compat RAND test exit normally so its inherited RNG is freed under valgrind as on master --- tests/api/test_ossl_rand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index fcc83ad11bb..5e6e1ff20b0 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -230,7 +230,7 @@ int test_wolfSSL_RAND_bytes(void) ok = (RAND_bytes(randbuf, sizeof(randbuf)) == 1); n_written = write(pipefds[1], randbuf, sizeof(randbuf)); close(pipefds[1]); - _exit((ok && n_written == sizeof(randbuf)) ? 0 : 1); + exit((ok && n_written == sizeof(randbuf)) ? 0 : 1); } else { /* Parent process. */