diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index d3ec20c7331..71dbfbb59f0 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -287,7 +287,19 @@ "configure": ["--enable-she=standard", "--enable-cmac"]}, {"name": "no-verify-oid-fpki", "minutes": 1.2, "configure": ["CPPFLAGS=-DNO_VERIFY_OID -DWOLFSSL_FPKI"]}, +{"name": "rng-atfork-off", "minutes": 1.2, + "comment": "Opt out of the RNG fork handlers that are on by default, so the lock without them is built and tested.", + "configure": ["--disable-rng-atfork"]}, +{"name": "rng-no-getpid", "minutes": 1.5, + "comment": "Default RNG fork handlers without the pid check, so the child handler alone must make the forked child reseed in the compat RAND fork test.", + "configure": ["--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, +{"name": "rng-atfork-off-no-getpid", "minutes": 1.5, + "comment": "Neither the fork handlers nor the pid check: the compat RAND fork test must see the child repeat the parent.", + "configure": ["--disable-rng-atfork", "--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, {"name": "no-verify-oid", "minutes": 1.1, "configure": ["CPPFLAGS=-DNO_VERIFY_OID"]}, +{"name": "rng-lock-off", "minutes": 1.1, + "comment": "Opt out of the per-instance RNG lock, so the WC_RNG layout and generate path without it are built and tested.", + "configure": ["--disable-rng-lock"]}, {"name": "rng-seed-device", "minutes": 1.1, "comment": "Seed the RNG from a nominated device. /dev/urandom stands in for a hardware RNG so the WC_RNG_SEED_DEVICE read path is actually exercised on a runner.", "configure": ["--with-rng-seed-device=/dev/urandom"]}, diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 6ab44d93c9c..fa25f0769b7 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 @@ -1289,6 +1291,7 @@ __MICROBLAZE__ __MINGW32__ __MINGW64_VERSION_MAJOR __MINGW64__ +__MSYS__ __MWERKS__ __NT__ __OS2__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 8263cd43ab9..2d743520083 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,63 @@ if(NOT WOLFSSL_STATICMEMORY STREQUAL "no") endif() endif() +# 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: enabled where supported)" + "yes" "yes;no") +if(WOLFSSL_RNG_ATFORK) + set(RNG_ATFORK_NEEDS "") + 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_c_source_compiles(" + #define _GNU_SOURCE 1 + #include + #include + #include + int main(void) { + Dl_info info; + const char* name; + if (dladdr((void*)(uintptr_t)main, &info) != 0) { + name = info.dli_fname; + if (name != NULL && name[0] != 0) + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + } + return 0; + }" HAVE_DLADDR) + set(CMAKE_REQUIRED_LIBRARIES "${RNG_ATFORK_SAVED_LIBS}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE "${RNG_ATFORK_SAVED_TARGET}") + if(NOT HAVE_PTHREAD_ATFORK) + set(RNG_ATFORK_NEEDS "pthread_atfork") + elseif(NOT HAVE_DLADDR) + set(RNG_ATFORK_NEEDS "dladdr and dlopen to pin the library against dlclose") + endif() + endif() + if(RNG_ATFORK_NEEDS) + message(STATUS "RNG fork handlers off: they need ${RNG_ATFORK_NEEDS}") + else() + list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_ATFORK" "-DWC_RNG_ATFORK_PIN") + list(APPEND WOLFSSL_LINK_LIBS ${CMAKE_DL_LIBS}) + endif() +endif() + # TLS (enabled by default; disable for wolfCrypt-only) add_option("WOLFSSL_TLS" "Enable TLS (default: enabled)" "yes" "yes;no") if(NOT WOLFSSL_TLS) diff --git a/cmake/options.h.in b/cmake/options.h.in index 0dba372e988..976dcf795dc 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -603,6 +603,12 @@ 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 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 b2ff85b3c7d..62acd778d0f 100644 --- a/configure.ac +++ b/configure.ac @@ -2754,6 +2754,50 @@ 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); 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: enabled where supported)])], + [ ENABLED_RNG_ATFORK=$enableval ], + [ ENABLED_RNG_ATFORK=yes ] + ) + +case "$ENABLED_RNG_ATFORK" in + yes|no) ;; + *) AC_MSG_ERROR([--enable-rng-atfork takes yes or no]) ;; +esac +if test "$ENABLED_RNG" = "no" || test "$ENABLED_RNG_LOCK" = "no" +then + if test "x$enable_rng_atfork" = "xyes" + then + AC_MSG_ERROR([--enable-rng-atfork requires --enable-rng and --enable-rng-lock]) + fi + ENABLED_RNG_ATFORK=no +fi + + # DTLS-SCTP AC_ARG_ENABLE([sctp], [AS_HELP_STRING([--enable-sctp],[Enable wolfSSL DTLS-SCTP support (default: disabled)])], @@ -13248,6 +13292,108 @@ 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 $CPPFLAGS $CFLAGS" in + *-DSINGLE_THREADED*) ENABLED_RNG_LOCK_THREADS=no ;; + *) ENABLED_RNG_LOCK_THREADS=yes ;; +esac +if test "$ENABLED_SINGLETHREADED" = "yes" +then + ENABLED_RNG_LOCK_THREADS=no +fi +if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG_LOCK_THREADS" = "no" +then + AC_MSG_ERROR([--enable-rng-lock requires threads]) +fi +# 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_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 + 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*) + 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, netRandom or memory zero checking" + fi + if test -z "$RNG_ATFORK_NEEDS" + then + saved_LIBS="$LIBS" + AC_SEARCH_LIBS([pthread_atfork], [pthread]) + LIBS="$saved_LIBS" + if test "$ac_cv_search_pthread_atfork" = "no" + then + RNG_ATFORK_NEEDS="pthread_atfork" + fi + fi + if test -z "$RNG_ATFORK_NEEDS" + then + # the handlers can never be removed, so the image holding them is + # 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*)(uintptr_t)main, &info) != 0) { + name = info.dli_fname; + if (name != NULL && name[0] != '\0') + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + } + ]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + RNG_ATFORK_NEEDS="dladdr and dlopen to pin the library against dlclose"]) + RNG_ATFORK_DL_LIBS="$LIBS" + LIBS="$saved_LIBS" + fi + if test -n "$RNG_ATFORK_NEEDS" + then + if test "x$enable_rng_atfork" = "xyes" + then + AC_MSG_ERROR([--enable-rng-atfork requires $RNG_ATFORK_NEEDS]) + fi + AC_MSG_NOTICE([RNG fork handlers off: they need $RNG_ATFORK_NEEDS]) + ENABLED_RNG_ATFORK=no + fi +fi +if test "$ENABLED_RNG_ATFORK" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_ATFORK -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 if test "$ac_cv_func_getpid" = "yes" then @@ -14029,6 +14175,7 @@ echo " * XCHACHA: $ENABLED_XCHACHA" echo " * Hash DRBG: $ENABLED_HASHDRBG" echo " * SHA-256 Hash DRBG: $ENABLED_SHA256_DRBG" echo " * SHA-512 Hash DRBG: $ENABLED_SHA512_DRBG" +echo " * RNG fork handlers: $ENABLED_RNG_ATFORK" echo " * MmemUse Entropy:" echo " * (AKA: wolfEntropy): $ENABLED_ENTROPY_MEMUSE" echo " * PWDBASED: $ENABLED_PWDBASED" diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index b07b2a2e6c3..8381f19baeb 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -56,8 +56,39 @@ 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. + 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 + 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 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, netRandom or memory zero + checking, whose locks the handlers cannot repair. + \return 0 on success. - \return MEMORY_E XMALLOC failed + \return MEMORY_E XMALLOC or the fork handler registration failed \return WINCRYPT_E wc_GenerateSeed: failed to acquire context \return CRYPTGEN_E wc_GenerateSeed: failed to get random \return BAD_FUNC_ARG wc_RNG_GenerateBlock input is null or sz exceeds @@ -66,6 +97,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 +141,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 +182,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 +217,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 +353,8 @@ WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap); \return 0 On success \return BAD_FUNC_ARG If rng is NULL \return MEMORY_E Memory allocation failed + \return BAD_MUTEX_E the lock that lets threads share this rng could not + be created \param rng Pointer to store WC_RNG pointer \param nonce Nonce buffer (can be NULL) @@ -359,6 +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 MEMORY_E the fork handlers could not be registered \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 MEMORY_E the fork handlers could not be registered \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 MEMORY_E the fork handlers could not be registered \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..0c0a8a99d77 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -436,6 +436,18 @@ extern "C" { #define CUSTOM_RAND_GENERATE_BLOCK my_rng_gen_block #endif +#if 0 /* Threaded build that never shares one WC_RNG between threads */ + #define WC_RNG_NO_LOCK +#endif +#if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ + /* configure probes for these; here they are asserted. Needs pthreads, + * a heap and the lock above (not WC_RNG_NO_LOCK); the pin needs dladdr + * and dlopen, -ldl on glibc before 2.34, and keeps the library mapped, + * since the handlers cannot be removed */ + #define WC_RNG_ATFORK + #define WC_RNG_ATFORK_PIN +#endif + /* ------------------------------------------------------------------------- */ /* Custom Standard Lib */ diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index c05805c1868..5e6e1ff20b0 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,30 +223,36 @@ int test_wolfSSL_RAND_bytes(void) ExpectIntGE(pid, 0); if (pid == 0) { ssize_t n_written = 0; + int ok; /* Child process. */ close(pipefds[0]); - RAND_bytes(randbuf, sizeof(randbuf)); + ok = (RAND_bytes(randbuf, sizeof(randbuf)) == 1); n_written = write(pipefds[1], randbuf, sizeof(randbuf)); close(pipefds[1]); - exit(n_written == sizeof(randbuf) ? 0 : 1); + exit((ok && n_written == sizeof(randbuf)) ? 0 : 1); } else { /* Parent process. */ byte childrand[8] = {0}; int waitstatus = 0; + int reaped; close(pipefds[1]); ExpectIntEQ(RAND_bytes(randbuf, sizeof(randbuf)), 1); ExpectIntEQ(read(pipefds[0], childrand, sizeof(childrand)), sizeof(childrand)); - #ifdef WOLFSSL_NO_GETPID + #if defined(WOLFSSL_NO_GETPID) && !defined(WC_RNG_LOCK_ATFORK) + /* nothing reseeds the child: neither the pid check nor the handlers */ ExpectBufEQ(randbuf, childrand, sizeof(randbuf)); #else ExpectBufNE(randbuf, childrand, sizeof(randbuf)); #endif close(pipefds[0]); - waitpid(pid, &waitstatus, 0); + /* reap first, whatever happened above; then judge it */ + reaped = (waitpid(pid, &waitstatus, 0) == pid); + ExpectIntEQ(reaped && WIFEXITED(waitstatus) && + WEXITSTATUS(waitstatus) == 0, 1); } RAND_cleanup(); #endif diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 2e396516740..e0d02258ea6 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -37,6 +37,10 @@ This library contains implementation for the random number generator. * WC_RNG_SEED_CB: Use custom seed callback function default: off * WC_RNG_BANK_SUPPORT: Enable RNG bank (pre-generated) default: off * random data support + * WC_RNG_NO_LOCK: Leave out the lock that lets threads default: off + * share one WC_RNG (lock on unless set) + * WC_RNG_ATFORK: pthread_atfork handlers so a forked default: on + * child can keep using its WC_RNG where found * WOLFSSL_RNG_USE_FULL_SEED: Use full-length seed for DRBG default: off * WOLFSSL_GENSEED_FORTEST: Use deterministic seed for testing default: off * WARNING: not for production use @@ -447,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) { @@ -485,6 +497,226 @@ static int UnlockDrbgState(void) #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ +#ifdef WC_RNG_LOCK_ATFORK +/* Every live lock, under drbgStateMutex. */ +static WC_RNG_LOCK* rngList = NULL; +/* 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. */ +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 && wc_LockMutex(&n->mutex) != 0) + n->broken = 1; + } +} + +/* After fork() in the parent: give back what prepare took. */ +static void RngAtForkParent(void) +{ + WC_RNG_LOCK* n; + if (!rngForkLocked) + return; + for (n = rngList; n != NULL; n = n->next) { + if (!n->broken) + (void)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; + } +} + +/* 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) +{ + int ret = LockDrbgState(); + if (ret != 0) + return ret; + if (!rngAtForkSet) { + /* pin outside the lock: dlopen() takes the loader lock */ + (void)UnlockDrbgState(); + wc_RngPinImage((void*)(wc_ptr_t)RngAtForkPrepare); + ret = LockDrbgState(); + if (ret != 0) + return ret; + } + if (!rngAtForkSet) { + if (pthread_atfork(RngAtForkPrepare, RngAtForkParent, + RngAtForkChild) != 0) + ret = MEMORY_E; + else + rngAtForkSet = 1; + } + (void)UnlockDrbgState(); + return ret; +} + +static int RngRegister(WC_RNG_LOCK* n) +{ + 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) + 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"); +} + +/* Creates the lock and registers it. */ +static int RngLockInit(WC_RNG* rng) +{ + int ret; + WC_RNG_LOCK* n = (WC_RNG_LOCK*)XMALLOC(sizeof(*n), rng->heap, + DYNAMIC_TYPE_RNG); + if (n == NULL) + return MEMORY_E; + XMEMSET(n, 0, sizeof(*n)); + n->heap = rng->heap; + if (wc_InitMutex(&n->mutex) != 0) { + XFREE(n, rng->heap, DYNAMIC_TYPE_RNG); + return BAD_MUTEX_E; + } +#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; + } + rng->lock = n; + return 0; +} + +/* Safe on a zeroed WC_RNG that never got a lock. */ +static void RngLockFree(WC_RNG* rng) +{ + WC_RNG_LOCK* n = rng->lock; + if (n == NULL) + return; + RngUnregister(n); + if (!n->noMutex) + (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; + if (rng->lock->broken || wc_LockMutex(&rng->lock->mutex) != 0) + return BAD_MUTEX_E; + return 0; +} + +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) +{ + if (rng->lockInited && wc_LockMutex(&rng->lock) != 0) + return BAD_MUTEX_E; + return 0; +} + +static void RngLockExit(WC_RNG* rng) +{ + if (rng->lockInited) + (void)wc_UnLockMutex(&rng->lock); +} +#else +#define RngLockEnter(rng) 0 +#define RngLockExit(rng) WC_DO_NOTHING +#endif /* WC_RNG_HAVE_LOCK */ + static int wc_RNG_HealthTestLocal(WC_RNG* rng, int reseed, void* heap, int devId); @@ -704,6 +936,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 +946,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 +967,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 +2584,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 +2840,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 +2855,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 +2903,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 +2967,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..fb132c08a5e 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -26,6 +26,9 @@ #elif defined(__FreeBSD__) /* for __FreeBSD_version */ #include +#elif (defined(__CYGWIN__) || defined(__MSYS__)) && !defined(_GNU_SOURCE) + /* dladdr and Dl_info, for the RNG fork handler pin, hide behind it */ + #define _GNU_SOURCE 1 #endif /* @@ -115,8 +118,12 @@ Threading/Mutex options: #ifdef WOLFSSL_ASYNC_CRYPT #include #endif -#if defined(HAVE_HASHDRBG) && !defined(WC_NO_RNG) +#ifndef WC_NO_RNG + /* random.h defines HAVE_HASHDRBG itself, so no HAVE_HASHDRBG test here */ #include + #ifdef WC_RNG_LOCK_ATFORK + #include /* the pin, which the handlers require */ + #endif #endif #ifdef FREESCALE_LTC_TFM @@ -415,6 +422,24 @@ static WC_DECLARE_INIT_STATE(wolfcrypt_init_state); int aarch64_use_sb = 0; #endif +#ifdef WC_RNG_LOCK_ATFORK +#if !defined(RTLD_NOLOAD) || !defined(RTLD_NODELETE) + #error "WC_RNG_ATFORK_PIN needs RTLD_NOLOAD and RTLD_NODELETE" +#endif +/* The fork handlers can never be unregistered, so the image that holds them + * is pinned against dlclose() before they are registered. */ +WOLFSSL_LOCAL void wc_RngPinImage(void* fn) +{ + Dl_info info; + const char* name; /* a pointer on most libcs, an array on Cygwin */ + if (dladdr(fn, &info) == 0 || (name = info.dli_fname) == NULL || + dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY) == NULL) { + /* no handle means no dlopen() reaches this image, so no dlclose() can */ + WOLFSSL_MSG("RNG fork handlers: no dlopen handle, nothing to pin"); + } +} +#endif + /* Used to initialize state for wolfcrypt return 0 on success */ @@ -555,6 +580,13 @@ int wolfCrypt_Init(void) WOLFCRYPT_INIT_RAISE_BAD_STATE(); } #endif + #ifdef WC_RNG_LOCK_ATFORK + ret = wc_RngAtForkInit(); + if (ret != 0) { + WOLFSSL_MSG("RNG fork handler registration failed"); + WOLFCRYPT_INIT_RAISE_BAD_STATE(); + } + #endif #if defined(FREESCALE_LTC_TFM) || defined(FREESCALE_LTC_ECC) ret = ksdk_port_init(); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 2cfc25eecad..51887034cc6 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,23 @@ 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 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; + 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 +26975,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 +27195,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..7062d8cbbfa 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -38,6 +38,26 @@ #include #include +#ifndef WC_NO_RNG + /* for WC_RNG_HAVE_LOCK and WC_RNG_LOCK_ATFORK */ + #include +#endif + +/* Needs the lock, threads it can start, and a heap for the compare buffer. */ +#if defined(WC_RNG_HAVE_LOCK) && !defined(WOLFSSL_ASYNC_CRYPT) && \ + !defined(HAVE_INTEL_RDRAND) && !defined(WOLF_CRYPTO_CB_FIND) && \ + !(defined(WOLFSSL_SILABS_SE_ACCEL) && defined(WOLFSSL_SILABS_TRNG)) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_XILINX_CRYPT_VERSAL) && \ + (defined(WOLFSSL_PTHREADS) || \ + (defined(USE_WINDOWS_API) && !defined(_WIN32_WCE))) + #define WC_TEST_RNG_LOCK +#endif +/* The fork test needs a real process model on top of the handlers. */ +#if defined(WC_TEST_RNG_LOCK) && defined(WC_RNG_LOCK_ATFORK) && \ + (defined(__unix__) || defined(__APPLE__) || defined(__linux__)) + #define WC_TEST_RNG_FORK +#endif #ifdef HAVE_STACK_SIZE THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args); @@ -251,6 +271,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..827432995aa 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -77,6 +77,49 @@ #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. */ +#if !defined(WC_RNG_NO_LOCK) && !defined(SINGLE_THREADED) && \ + !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_BSDKM) && \ + defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + #define WC_RNG_HAVE_LOCK +#endif + +/* A forked child cannot release a lock another thread held; POSIX only lets + * 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, 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(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 + +#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; + 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 */ +} WC_RNG_LOCK; +WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ +WOLFSSL_LOCAL void wc_RngPinImage(void* fn); /* keeps fn's image mapped */ +#endif + /* avoid redefinition of structs */ #if !defined(HAVE_FIPS) || \ @@ -426,6 +469,14 @@ struct WC_RNG { #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) int devId; #endif +#ifdef WC_RNG_LOCK_ATFORK + /* NULL until wc_InitRng succeeds. Initialize only a new or freed WC_RNG: + * wc_InitRng over a live one leaks this and 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 }; #endif /* NO FIPS or have FIPS v2*/ @@ -450,6 +501,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);