diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index d3ec20c7331..4851d4ca1f2 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -248,6 +248,12 @@ "--enable-experimental"]}, {"name": "wolfssl-extra", "minutes": 1.6, "configure": ["CPPFLAGS=-DWOLFSSL_EXTRA"]}, {"name": "coding-no", "minutes": 1.5, "configure": ["--enable-coding=no"]}, +{"name": "rng-no-getpid", "minutes": 1.5, + "comment": "Default RNG fork handlers without the pid check, so the child handler alone must make the forked child reseed in the compat RAND fork test.", + "configure": ["--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, +{"name": "rng-atfork-off-no-getpid", "minutes": 1.5, + "comment": "Neither the fork handlers nor the pid check: the compat RAND fork test must see the child repeat the parent.", + "configure": ["--disable-rng-atfork", "--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_GETPID"]}, {"name": "she-ext-cmac-cryptocb", "minutes": 1.3, "configure": ["--enable-she=extended", "--enable-cmac", "--enable-cryptocb", "--enable-cryptocbutils"]}, @@ -283,11 +289,23 @@ {"name": "no-tls-cryptocb-aesgcm-setkey-free", "minutes": 1.3, "configure": ["--disable-tls", "--enable-cryptocb", "--enable-aesgcm", "CPPFLAGS=-DWOLF_CRYPTO_CB_AES_SETKEY -DWOLF_CRYPTO_CB_FREE"]}, +{"name": "rng-bank-lock", "minutes": 1.3, + "comment": "The RNG bank with the per-instance lock: bank builds still hand out plain instances.", + "configure": ["--enable-rng-bank", "--enable-opensslextra"]}, {"name": "she-std-cmac", "minutes": 1.2, "configure": ["--enable-she=standard", "--enable-cmac"]}, {"name": "no-verify-oid-fpki", "minutes": 1.2, "configure": ["CPPFLAGS=-DNO_VERIFY_OID -DWOLFSSL_FPKI"]}, +{"name": "rng-single-threaded", "minutes": 1.2, + "comment": "SINGLE_THREADED: no RNG lock and no fork handlers may be built, and the compat RAND test still passes.", + "configure": ["--enable-singlethreaded", "--enable-opensslextra"]}, +{"name": "rng-atfork-off", "minutes": 1.2, + "comment": "Opt out of the RNG fork handlers that are on by default, so the lock without them is built and tested.", + "configure": ["--disable-rng-atfork"]}, {"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..fafa365c943 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -561,6 +561,7 @@ PKA_SR_OPERRF PLATFORMIO PLUTON_CRYPTO_ECC PRINT_SESSION_STATS +PTHREAD_CANCEL_DISABLE PTHREAD_STACK_MIN QAT_ENABLE_HASH QAT_ENABLE_RNG @@ -606,6 +607,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 +1292,7 @@ __MICROBLAZE__ __MINGW32__ __MINGW64_VERSION_MAJOR __MINGW64__ +__MSYS__ __MWERKS__ __NT__ __OS2__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b48daa5a1e..d22d691959a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,10 +144,13 @@ check_function_exists("memset" HAVE_MEMSET) check_function_exists("socket" HAVE_SOCKET) check_function_exists("strftime" HAVE_STRFTIME) check_function_exists("__atomic_fetch_add" HAVE_C___ATOMIC) -check_function_exists("getpid" HAVE_GETPID) include(CheckSymbolExists) check_symbol_exists(isascii "ctype.h" HAVE_ISASCII) +check_symbol_exists(getpid "unistd.h" HAVE_GETPID) # pid_t comes with it +if(HAVE_GETPID) + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_GETPID") # as options.h says +endif() include(CheckTypeSize) @@ -3765,6 +3768,15 @@ if(WOLFSSL_RNG_BANK) list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_BANK_SUPPORT") endif() +# RNG lock (share one WC_RNG between threads) +add_option("WOLFSSL_RNG_LOCK" + "Enable the lock that lets one WC_RNG be shared between threads (default: enabled where the build supports it)" + "yes" "yes;no") +# the header leaves the lock out where threads or the RNG are absent +if(NOT WOLFSSL_RNG_LOCK) + list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_NO_LOCK") +endif() + # Valgrind (for unit tests) add_option("WOLFSSL_VALGRIND" "Enable valgrind for unit tests (default: disabled)" @@ -3993,6 +4005,73 @@ if(NOT WOLFSSL_STATICMEMORY STREQUAL "no") endif() endif() +# RNG fork handlers (a forked child keeps using its WC_RNG); a build that +# cannot carry them drops them. After the static memory option: it reads it. +add_option("WOLFSSL_RNG_ATFORK" + "Enable pthread_atfork handlers so a forked child can keep using a WC_RNG (default: enabled where supported)" + "yes" "yes;no") +if(WOLFSSL_RNG_ATFORK) + set(RNG_ATFORK_NEEDS "") + if(WOLFSSL_USER_SETTINGS) + set(RNG_ATFORK_NEEDS "user_settings.h to define them") + elseif(WOLFSSL_SINGLE_THREADED OR WOLFSSL_LINUX_KM) + set(RNG_ATFORK_NEEDS "threads and no kernel module") + elseif(NOT WOLFSSL_RNG OR NOT WOLFSSL_RNG_LOCK OR NOT WOLFSSL_HASH_DRBG OR + WOLFSSL_RNG_BANK OR WOLFSSL_STATICMEMORY) + set(RNG_ATFORK_NEEDS "the RNG, its lock and the Hash DRBG, and no RNG bank or static memory") + else() + # real link tests: a toolchain whose try-compile only builds a static + # library reports every function as found + set(RNG_ATFORK_SAVED_LIBS "${CMAKE_REQUIRED_LIBRARIES}") + set(RNG_ATFORK_SAVED_TARGET "${CMAKE_TRY_COMPILE_TARGET_TYPE}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + check_function_exists("pthread_atfork" WOLFSSL_HAVE_PTHREAD_ATFORK) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) + check_c_source_compiles(" + #if defined(__linux__) || defined(__ANDROID__) || \ + defined(__CYGWIN__) || defined(__MSYS__) + #define _GNU_SOURCE 1 /* as wc_port.c does */ + #endif + #include + #include + #include + #include + #include + #ifdef __APPLE__ + #error macOS has only named semaphores, which a forked child shares + #endif + int main(void) { + Dl_info info; + const char* name; + sem_t s; + int old; + if (sem_init(&s, 0, 1) == 0) + (void)sem_post(&s); + (void)pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old); + if (dladdr((void*)(uintptr_t)main, &info) != 0) { + name = info.dli_fname; + if (name != NULL && name[0] != 0) + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + } + return 0; + }" WOLFSSL_HAVE_DLADDR_PIN) + set(CMAKE_REQUIRED_LIBRARIES "${RNG_ATFORK_SAVED_LIBS}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE "${RNG_ATFORK_SAVED_TARGET}") + if(NOT WOLFSSL_HAVE_PTHREAD_ATFORK) + set(RNG_ATFORK_NEEDS "pthread_atfork") + elseif(NOT WOLFSSL_HAVE_DLADDR_PIN) + set(RNG_ATFORK_NEEDS "dladdr, dlopen, unnamed semaphores and thread cancellation") + endif() + endif() + if(RNG_ATFORK_NEEDS) + message(STATUS "RNG fork handlers off: they need ${RNG_ATFORK_NEEDS}") + else() + list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_ATFORK") + list(APPEND WOLFSSL_LINK_LIBS ${CMAKE_DL_LIBS}) + endif() +endif() + # TLS (enabled by default; disable for wolfCrypt-only) add_option("WOLFSSL_TLS" "Enable TLS (default: enabled)" "yes" "yes;no") if(NOT WOLFSSL_TLS) @@ -4162,6 +4241,7 @@ generate_build_flags() if(WOLFSSL_USER_SETTINGS) # Replace all options and just use WOLFSSL_USER_SETTINGS set(WOLFSSL_DEFINITIONS "-DWOLFSSL_USER_SETTINGS") + set(HAVE_GETPID OFF) # user_settings.h decides; keeps options.h in step endif() if(WOLFSSL_USER_SETTINGS_ASM) diff --git a/cmake/options.h.in b/cmake/options.h.in index 0dba372e988..e61eba58ec0 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 HAVE_GETPID +#cmakedefine HAVE_GETPID #undef HAVE_VALGRIND #cmakedefine HAVE_VALGRIND #undef HAVE_CRL_MONITOR diff --git a/configure.ac b/configure.ac index 5e303026b33..0b179bce23e 100644 --- a/configure.ac +++ b/configure.ac @@ -2754,6 +2754,48 @@ then fi +# RNG lock (share one WC_RNG between threads) +AC_ARG_ENABLE([rng-lock], + [AS_HELP_STRING([--enable-rng-lock],[Enable the lock that lets one WC_RNG be shared between threads (default: enabled where the build supports it)])], + [ ENABLED_RNG_LOCK=$enableval ], + [ ENABLED_RNG_LOCK=yes ] + ) + +case "$ENABLED_RNG_LOCK" in + yes|no) ;; + *) AC_MSG_ERROR([--enable-rng-lock takes yes or no]) ;; +esac +# only an explicit ask is an error: the default is yes +if test "x$enable_rng_lock" = "xyes" && test "$ENABLED_RNG" = "no" +then + AC_MSG_ERROR([--enable-rng-lock requires --enable-rng]) +fi +if test "$ENABLED_RNG_LOCK" = "no" +then + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_NO_LOCK" +fi + +# RNG fork handlers (a forked child keeps using its WC_RNG); decided below +AC_ARG_ENABLE([rng-atfork], + [AS_HELP_STRING([--enable-rng-atfork],[Enable pthread_atfork handlers so a forked child can keep using a WC_RNG (default: enabled where supported)])], + [ ENABLED_RNG_ATFORK=$enableval ], + [ ENABLED_RNG_ATFORK=yes ] + ) + +case "$ENABLED_RNG_ATFORK" in + yes|no) ;; + *) AC_MSG_ERROR([--enable-rng-atfork takes yes or no]) ;; +esac +if test "$ENABLED_RNG" = "no" || test "$ENABLED_RNG_LOCK" = "no" +then + if test "x$enable_rng_atfork" = "xyes" + then + AC_MSG_ERROR([--enable-rng-atfork requires --enable-rng and --enable-rng-lock]) + fi + ENABLED_RNG_ATFORK=no +fi + + # DTLS-SCTP AC_ARG_ENABLE([sctp], [AS_HELP_STRING([--enable-sctp],[Enable wolfSSL DTLS-SCTP support (default: disabled)])], @@ -13250,6 +13292,130 @@ then AM_CFLAGS="$AM_CFLAGS -DHAVE___UINT128_T=1" fi +# RNG lock and fork handlers need threads; the handlers need everything the +# WC_RNG_LOCK_ATFORK gate in random.h asks for +case "$AM_CFLAGS $CPPFLAGS $CFLAGS $EXTRA_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 +case "$AM_CFLAGS $CPPFLAGS $CFLAGS $EXTRA_CFLAGS" in + *-DCUSTOM_RAND_GENERATE_BLOCK*) ENABLED_RNG_LOCK_CUSTOM=yes ;; + *) ENABLED_RNG_LOCK_CUSTOM=no ;; +esac +if test "$ENABLED_RNG_LOCK_THREADS" = "no" || test "$ENABLED_RNG" = "no" || \ + test "$ENABLED_RNG_LOCK_CUSTOM" = "yes" || \ + test "$ENABLED_SELFTEST" = "yes" || test "x$ENABLED_HASHDRBG" != "xyes" || \ + test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" || \ + { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } +then + if test "x$enable_rng_lock" = "xyes" + then + AC_MSG_ERROR([--enable-rng-lock requires threads and the Hash DRBG, and none of selftest, FIPS before v7, a kernel module or a custom rand block]) + fi + ENABLED_RNG_LOCK=no +fi +# a build that cannot carry the handlers drops them, unless asked explicitly +if test "$ENABLED_RNG_ATFORK" = "yes" +then + RNG_ATFORK_NEEDS="" + if test "$ENABLED_USERSETTINGS" = "yes" + then + # user_settings.h owns every define in that mode + RNG_ATFORK_NEEDS="user_settings.h to define WC_RNG_ATFORK" + fi + if test "$ENABLED_RNG_LOCK_THREADS" = "no" || \ + test "$ENABLED_LINUXKM" = "yes" || test "$ENABLED_BSDKM" = "yes" + then + RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }threads and no kernel module" + fi + case "$AM_CFLAGS $CPPFLAGS $CFLAGS $EXTRA_CFLAGS" in + *-DWOLFSSL_STATIC_MEMORY*|*-DWOLFSSL_NO_MALLOC*|*-DCUSTOM_RAND_GENERATE_BLOCK*|*-DWOLFSSL_CHECK_MEM_ZERO*|*-DWOLFSSL_TRACK_MEMORY*|*-DWOLFSSL_MEM_FAIL_COUNT*|*-DWOLFSSL_USER_MUTEX*) + ENABLED_RNG_ATFORK_FLAGS=no ;; + *) ENABLED_RNG_ATFORK_FLAGS=yes ;; + esac + if test "$ENABLED_SELFTEST" = "yes" || test "x$ENABLED_HASHDRBG" != "xyes" || \ + test "$ENABLED_ENTROPY_MEMUSE" != "no" || test "$ENABLED_RNG_BANK" = "yes" || \ + test "$ENABLED_RNG_ATFORK_FLAGS" = "no" || test "$ENABLED_WNR" = "yes" || \ + { test "$ENABLED_FIPS" = "yes" && test "${HAVE_FIPS_VERSION_MAJOR:-0}" -lt 7; } + then + RNG_ATFORK_NEEDS="${RNG_ATFORK_NEEDS:+$RNG_ATFORK_NEEDS, and }the Hash DRBG with a heap and none of selftest, FIPS before v7, entropy-memuse, rng-bank, static memory, netRandom, memory zero checking or memory tracking" + fi + if test -z "$RNG_ATFORK_NEEDS" + then + saved_LIBS="$LIBS" + AC_SEARCH_LIBS([pthread_atfork], [pthread]) + LIBS="$saved_LIBS" # detection only: AX_PTHREAD already added the library + if test "$ac_cv_search_pthread_atfork" = "no" + then + RNG_ATFORK_NEEDS="pthread_atfork" + fi + fi + if test -z "$RNG_ATFORK_NEEDS" + then + # link the real pin: a bare symbol probe passes where the header + # hides dladdr or lacks the RTLD flags + saved_LIBS="$LIBS" + AC_SEARCH_LIBS([dladdr], [dl]) + AC_MSG_CHECKING([whether dladdr, dlopen, unnamed semaphores and thread cancellation are available]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #if defined(__linux__) || defined(__ANDROID__) || \ + defined(__CYGWIN__) || defined(__MSYS__) + #define _GNU_SOURCE 1 /* as wc_port.c does */ + #endif + #include + #include + #include + #include + #include + #ifdef __APPLE__ + #error "macOS has only named semaphores, which a forked child shares" + #endif + ]], [[ + Dl_info info; + const char* name; + sem_t s; + int old; + if (sem_init(&s, 0, 1) == 0) + (void)sem_post(&s); + (void)pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old); + if (dladdr((void*)(uintptr_t)main, &info) != 0) { + name = info.dli_fname; + if (name != NULL && name[0] != '\0') + (void)dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY); + } + ]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + RNG_ATFORK_NEEDS="dladdr, dlopen, unnamed semaphores and thread cancellation"]) + 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" + case "$ac_cv_search_dladdr" in # -ldl where dladdr needed it + -l*) LIBS="$ac_cv_search_dladdr $LIBS" + PC_LIBS_PRIVATE="$PC_LIBS_PRIVATE $ac_cv_search_dladdr" ;; + esac +fi + # Add HAVE_GETPID to AM_CFLAGS for inclusion in options.h if test "$ac_cv_func_getpid" = "yes" then @@ -14031,6 +14197,8 @@ echo " * XCHACHA: $ENABLED_XCHACHA" echo " * Hash DRBG: $ENABLED_HASHDRBG" echo " * SHA-256 Hash DRBG: $ENABLED_SHA256_DRBG" echo " * SHA-512 Hash DRBG: $ENABLED_SHA512_DRBG" +echo " * RNG lock: $ENABLED_RNG_LOCK" +echo " * RNG fork handlers: $ENABLED_RNG_ATFORK" echo " * MmemUse Entropy:" echo " * (AKA: wolfEntropy): $ENABLED_ENTROPY_MEMUSE" echo " * PWDBASED: $ENABLED_PWDBASED" diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index b07b2a2e6c3..80c48249a05 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -56,8 +56,32 @@ int wc_FreeNetRandom(void); (deterministic random bit generator) allocated (should be deallocated with wc_FreeRng). This is a blocking operation. + One WC_RNG may be shared between threads: each generate and reseed holds + the instance lock. WC_RNG_NO_LOCK (configure --disable-rng-lock) leaves + the lock out. Every backend but a crypto callback runs with the lock + held; a callback answers first, so it may fall back to the same instance. + A seed callback runs with the lock held and must not use the RNG API. + wc_InitRng*() and wc_FreeRng() do not lock; initialize only a new or + freed WC_RNG, with no other thread using it. + + POSIX lets a forked child of a threaded process only exec. Where the + build has pthread_atfork(), unnamed POSIX semaphores and the dladdr() + pin, fork handlers let the child keep using its WC_RNG: the parent holds + every lock across fork() and the child releases them and reseeds, so + each fork() waits for every live instance's generate in flight. The + child should use an instance it already has: wc_InitRng() there still + waits on a mutex the handlers do not cover. --disable-rng-atfork leaves + them out; a user_settings build defines WC_RNG_ATFORK to turn them on. + The child's reseed uses the configured allocator and seed source, which + must therefore work after fork(). wolfCrypt_Init() or the first + wc_InitRng() registers them; they can never be unregistered, + so the library pins itself against dlclose(). They cover WC_RNG locks + only, not clone(), vfork() or _Fork(). A fork() from inside a seed or + hash callback deadlocks. Builds without them, macOS among them, leave a + forked child only exec(). + \return 0 on success. - \return MEMORY_E XMALLOC failed + \return MEMORY_E XMALLOC or the fork handler registration failed \return WINCRYPT_E wc_GenerateSeed: failed to acquire context \return CRYPTGEN_E wc_GenerateSeed: failed to get random \return BAD_FUNC_ARG wc_RNG_GenerateBlock input is null or sz exceeds @@ -66,6 +90,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 +134,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 +175,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 +210,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 +346,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 +390,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 +416,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 +443,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 +493,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..58599160109 100644 --- a/examples/configs/user_settings_template.h +++ b/examples/configs/user_settings_template.h @@ -436,6 +436,23 @@ extern "C" { #define CUSTOM_RAND_GENERATE_BLOCK my_rng_gen_block #endif +#if 0 /* Threaded build that never shares one WC_RNG between threads */ + #undef WC_RNG_NO_LOCK + #define WC_RNG_NO_LOCK +#endif +#if 0 /* pthread_atfork handlers: a forked child keeps using its WC_RNG */ + /* configure probes for this; here it is asserted. Needs pthreads, + * a heap, the lock above (not WC_RNG_NO_LOCK) and unnamed POSIX + * semaphores (sem_init, so not macOS) and pthread_setcancelstate (so + * not Android); the pin needs dladdr and dlopen, + * -ldl on glibc before 2.34, and keeps the library mapped, since the + * handlers cannot be removed. Left out with SINGLE_THREADED, + * entropy-memuse, the RNG bank, netRandom, selftest, FIPS before v7, + * memory zero checking, memory tracking or failure counting */ + #undef WC_RNG_ATFORK + #define WC_RNG_ATFORK +#endif + /* ------------------------------------------------------------------------- */ /* Custom Standard Lib */ diff --git a/tests/api/test_ossl_rand.c b/tests/api/test_ossl_rand.c index c05805c1868..039bcbe5169 100644 --- a/tests/api/test_ossl_rand.c +++ b/tests/api/test_ossl_rand.c @@ -21,7 +21,9 @@ #include -#if defined(__linux__) || defined(__FreeBSD__) +/* the same guard as the fork test below */ +#if defined(OPENSSL_EXTRA) && defined(HAVE_GETPID) && !defined(__MINGW64__) && \ + !defined(__MINGW32__) #include #include #endif @@ -184,6 +186,7 @@ int test_wolfSSL_RAND_bytes(void) byte randbuf[8] = {0}; int pipefds[2] = {0}; pid_t pid = 0; + int piped; #endif /* sanity check */ @@ -213,35 +216,46 @@ int test_wolfSSL_RAND_bytes(void) /* No global methods set. */ ExpectIntEQ(RAND_seed(seed, sizeof(seed)), 1); - ExpectIntEQ(pipe(pipefds), 0); - pid = fork(); + piped = (pipe(pipefds) == 0); + ExpectIntEQ(piped, 1); + pid = piped ? fork() : -1; ExpectIntGE(pid, 0); if (pid == 0) { ssize_t n_written = 0; + int ok; /* Child process. */ close(pipefds[0]); - RAND_bytes(randbuf, sizeof(randbuf)); + ok = (RAND_bytes(randbuf, sizeof(randbuf)) == 1); n_written = write(pipefds[1], randbuf, sizeof(randbuf)); close(pipefds[1]); - exit(n_written == sizeof(randbuf) ? 0 : 1); + exit((ok && n_written == sizeof(randbuf)) ? 0 : 1); } - else { + else if (pid > 0) { /* Parent process. */ byte childrand[8] = {0}; int waitstatus = 0; + int reaped; close(pipefds[1]); ExpectIntEQ(RAND_bytes(randbuf, sizeof(randbuf)), 1); ExpectIntEQ(read(pipefds[0], childrand, sizeof(childrand)), sizeof(childrand)); - #ifdef WOLFSSL_NO_GETPID + #if defined(WOLFSSL_NO_GETPID) && !defined(WC_RNG_LOCK_ATFORK) + /* nothing reseeds the child: neither the pid check nor the handlers */ ExpectBufEQ(randbuf, childrand, sizeof(randbuf)); #else ExpectBufNE(randbuf, childrand, sizeof(randbuf)); #endif close(pipefds[0]); - waitpid(pid, &waitstatus, 0); + /* reap first, whatever happened above; then judge it */ + reaped = (waitpid(pid, &waitstatus, 0) == pid); + ExpectIntEQ(reaped && WIFEXITED(waitstatus) && + WEXITSTATUS(waitstatus) == 0, 1); + } + else if (piped) { + close(pipefds[0]); + close(pipefds[1]); } RAND_cleanup(); #endif diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 2e396516740..c1e3e1f6411 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -37,6 +37,10 @@ This library contains implementation for the random number generator. * WC_RNG_SEED_CB: Use custom seed callback function default: off * WC_RNG_BANK_SUPPORT: Enable RNG bank (pre-generated) default: off * random data support + * WC_RNG_NO_LOCK: Leave out the lock that lets threads default: off + * share one WC_RNG (lock on unless set) + * WC_RNG_ATFORK: pthread_atfork handlers so a forked default: on + * child can keep using its WC_RNG where found * WOLFSSL_RNG_USE_FULL_SEED: Use full-length seed for DRBG default: off * WOLFSSL_GENSEED_FORTEST: Use deterministic seed for testing default: off * WARNING: not for production use @@ -135,6 +139,9 @@ This library contains implementation for the random number generator. #include +#ifdef WC_RNG_LOCK_ATFORK + #include /* for the fork handlers, whatever the seed source */ +#endif #ifdef WC_RNG_BANK_SUPPORT #include #endif @@ -485,6 +492,284 @@ static int UnlockDrbgState(void) #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ +#ifdef WC_RNG_LOCK_ATFORK +static WC_RNG_LOCK* rngList = NULL; /* every live lock, under rngListSem */ +static sem_t rngListSem; +static int rngAtForkSet = 0; /* handlers registered, never unregistered */ +static int rngImagePinned = 0; /* so a failed registration does not re-pin */ +static int rngListDead = 0; /* registry unusable: every handler backs off */ + +/* sem_wait() is a cancellation point; a cancel here would strand the lock. */ +static int RngSemWait(sem_t* s) +{ + int ret = 0; + int old = PTHREAD_CANCEL_ENABLE; + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); + while (sem_wait(s) != 0) { + if (errno != EINTR) { + ret = BAD_MUTEX_E; + break; + } + } + (void)pthread_setcancelstate(old, NULL); + return ret; +} + +/* Before fork(): the forking thread takes the registry and every lock. */ +static void RngAtForkPrepare(void) +{ + WC_RNG_LOCK* n; + if (rngListDead) + return; + if (RngSemWait(&rngListSem) != 0) { + rngListDead = 1; /* nothing held, and never again */ + return; + } + for (n = rngList; n != NULL; n = n->next) { + if (!n->broken && RngSemWait(&n->sem) != 0) + n->broken = 1; + } +} + +/* After fork() in the parent: give back what prepare took. */ +static void RngAtForkParent(void) +{ + WC_RNG_LOCK* n; + if (rngListDead) + return; + for (n = rngList; n != NULL; n = n->next) { + if (!n->broken) + (void)sem_post(&n->sem); + } + (void)sem_post(&rngListSem); +} + +/* Child after fork(): stores and sem_post() only, all POSIX allows here. + * Every DRBG reseeds next; if prepare held nothing, everything fails closed. */ +static void RngAtForkChild(void) +{ + WC_RNG_LOCK* n; + for (n = rngList; n != NULL; n = n->next) { /* forward links stay whole */ + if (rngListDead) { + n->broken = 1; + continue; + } + #ifndef NO_SHA256 + if (n->drbg != NULL) + ((DRBG_internal*)n->drbg)->reseedCtr = WC_RESEED_INTERVAL; + #endif + #ifdef WOLFSSL_DRBG_SHA512 + if (n->drbg512 != NULL) + ((DRBG_SHA512_internal*)n->drbg512)->reseedCtr = + WC_RESEED_INTERVAL; + #endif + if (!n->broken) + (void)sem_post(&n->sem); + } + if (!rngListDead) + (void)sem_post(&rngListSem); +} + +/* Registers the handlers once; the pin runs outside drbgStateMutex. */ +WOLFSSL_LOCAL int wc_RngAtForkInit(void) +{ + int ret = LockDrbgState(); + if (ret != 0) + return ret; + if (!rngAtForkSet && !rngImagePinned) { + /* pin outside the lock: dlopen() takes the loader lock. Racing + * first callers may both pin, which is harmless. */ + (void)UnlockDrbgState(); + wc_RngPinImage((void*)(wc_ptr_t)RngAtForkPrepare); + ret = LockDrbgState(); + if (ret != 0) + return ret; + rngImagePinned = 1; + } + if (!rngAtForkSet) { + ret = (sem_init(&rngListSem, 0, 1) == 0) ? 0 : BAD_MUTEX_E; + if (ret == 0 && pthread_atfork(RngAtForkPrepare, RngAtForkParent, + RngAtForkChild) != 0) { + (void)sem_destroy(&rngListSem); + ret = MEMORY_E; + } + if (ret == 0) + rngAtForkSet = 1; + } + (void)UnlockDrbgState(); + return ret; +} + +/* Creates the lock under the registry and links the node. */ +static int RngRegister(WC_RNG_LOCK* n) +{ + int ret = wc_RngAtForkInit(); + if (ret != 0) + return ret; + if (rngListDead) { + WOLFSSL_MSG("RngRegister: registry dead since a fork"); + return BAD_MUTEX_E; + } + if (RngSemWait(&rngListSem) != 0) + return BAD_MUTEX_E; + ret = (sem_init(&n->sem, 0, 1) == 0) ? 0 : BAD_MUTEX_E; + if (ret == 0) { + n->next = rngList; + n->prev = &rngList; + if (rngList != NULL) + rngList->prev = &n->next; + rngList = n; + } + (void)sem_post(&rngListSem); + return ret; +} + +/* Without the registry the node is leaked, broken and pointing at nothing. */ +static int RngUnregister(WC_RNG_LOCK* n) +{ + if (rngListDead || RngSemWait(&rngListSem) != 0) { + n->broken = 1; + n->drbg = NULL; + n->drbg512 = NULL; + WOLFSSL_MSG("RngUnregister: registry unavailable, node leaked"); + return BAD_MUTEX_E; + } + *n->prev = n->next; + if (n->next != NULL) + n->next->prev = n->prev; + (void)sem_post(&rngListSem); + return 0; +} + +/* Allocates the node and registers it. */ +static int RngLockInit(WC_RNG* rng) +{ + int ret; + WC_RNG_LOCK* n = (WC_RNG_LOCK*)XMALLOC(sizeof(*n), rng->heap, + DYNAMIC_TYPE_RNG); + if (n == NULL) + return MEMORY_E; + XMEMSET(n, 0, sizeof(*n)); + n->heap = rng->heap; +#ifndef NO_SHA256 + n->drbg = rng->drbg; +#endif +#ifdef WOLFSSL_DRBG_SHA512 + n->drbg512 = rng->drbg512; +#endif + ret = RngRegister(n); + if (ret != 0) { + XFREE(n, rng->heap, DYNAMIC_TYPE_RNG); + return ret; + } + rng->lock = n; + return 0; +} + +/* Safe on a zeroed WC_RNG that never got a lock. */ +static void RngLockFree(WC_RNG* rng) +{ + WC_RNG_LOCK* n = rng->lock; + if (n == NULL) + return; + if (RngUnregister(n) == 0) { + (void)sem_destroy(&n->sem); + XFREE(n, n->heap, DYNAMIC_TYPE_RNG); + } + rng->lock = NULL; +} + +/* Cancellation stays off while the lock is held: a reseed reads a device, + * a cancellation point, and a cancelled holder would strand every fork(). */ +static int RngLockEnter(WC_RNG* rng) +{ + int old = PTHREAD_CANCEL_ENABLE; + int ret; + if (rng->lock == NULL) + return 0; + if (rng->lock->broken) + return BAD_MUTEX_E; + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); + ret = RngSemWait(&rng->lock->sem); + if (ret != 0) + (void)pthread_setcancelstate(old, NULL); + else + rng->lock->cancel = old; + return ret; +} + +static void RngLockExit(WC_RNG* rng) +{ + int old; + if (rng->lock == NULL) + return; + old = rng->lock->cancel; + (void)sem_post(&rng->lock->sem); + (void)pthread_setcancelstate(old, NULL); +} +#elif defined(WC_RNG_HAVE_LOCK) +/* Without fork handlers the lock lives in the WC_RNG itself: no heap. */ +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; + } +} + +/* Cancellation stays off while the lock is held, where the platform has + * cancellation at all: a reseed reads a device, a cancellation point. */ +static int RngLockEnter(WC_RNG* rng) +{ +#ifdef PTHREAD_CANCEL_DISABLE + int old = PTHREAD_CANCEL_ENABLE; +#endif + if (!rng->lockInited) + return 0; +#ifdef PTHREAD_CANCEL_DISABLE + (void)pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); +#endif + if (wc_LockMutex(&rng->lock) != 0) { + #ifdef PTHREAD_CANCEL_DISABLE + (void)pthread_setcancelstate(old, NULL); + #endif + return BAD_MUTEX_E; + } +#ifdef PTHREAD_CANCEL_DISABLE + rng->lockCancel = old; +#endif + return 0; +} + +static void RngLockExit(WC_RNG* rng) +{ +#ifdef PTHREAD_CANCEL_DISABLE + int old; +#endif + if (!rng->lockInited) + return; +#ifdef PTHREAD_CANCEL_DISABLE + old = rng->lockCancel; /* read before the unlock hands the slot on */ +#endif + (void)wc_UnLockMutex(&rng->lock); +#ifdef PTHREAD_CANCEL_DISABLE + (void)pthread_setcancelstate(old, NULL); +#endif +} +#else +#define RngLockEnter(rng) 0 +#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 +989,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 +999,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 +1020,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 @@ -1894,6 +2191,10 @@ int wc_Sha512Drbg_IsDisabled(void) } #endif /* WOLFSSL_DRBG_SHA512 */ #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ +#else + /* no Hash DRBG, so no lock for the backends to hold */ + #define RngLockEnter(rng) 0 + #define RngLockExit(rng) WC_DO_NOTHING #endif /* HAVE_HASHDRBG */ /* End NIST DRBG Code */ @@ -2340,6 +2641,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; } @@ -2542,6 +2850,7 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) return 0; #ifdef WOLF_CRYPTO_CB + /* before the lock: a callback may fall back to this same instance */ #ifndef WOLF_CRYPTO_CB_FIND if (rng->devId != INVALID_DEVID) #endif @@ -2553,22 +2862,35 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) } #endif + ret = RngLockEnter(rng); /* held across every other backend */ + if (ret != 0) + return ret; + #ifdef HAVE_INTEL_RDRAND - if (IS_INTEL_RDRAND(intel_flags)) - return wc_GenerateRand_IntelRD(NULL, output, sz); + if (IS_INTEL_RDRAND(intel_flags)) { + ret = wc_GenerateRand_IntelRD(NULL, output, sz); + RngLockExit(rng); + return ret; + } #endif #if defined(WOLFSSL_SILABS_SE_ACCEL) && defined(WOLFSSL_SILABS_TRNG) - return silabs_GenerateRand(output, sz); + ret = silabs_GenerateRand(output, sz); + RngLockExit(rng); + return ret; #endif #if defined(WOLFSSL_ASYNC_CRYPT) if (rng->asyncDev.marker == WOLFSSL_ASYNC_MARKER_RNG) { /* these are blocking */ #ifdef HAVE_CAVIUM - return NitroxRngGenerateBlock(rng, output, sz); + ret = NitroxRngGenerateBlock(rng, output, sz); + RngLockExit(rng); + return ret; #elif defined(HAVE_INTEL_QA) && defined(QAT_ENABLE_RNG) - return IntelQaDrbg(&rng->asyncDev, output, sz); + ret = IntelQaDrbg(&rng->asyncDev, output, sz); + RngLockExit(rng); + return ret; #else /* simulator not supported */ #endif @@ -2586,11 +2908,15 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) #else #ifdef HAVE_HASHDRBG - if (sz > RNG_MAX_BLOCK_LEN) + if (sz > RNG_MAX_BLOCK_LEN) { + RngLockExit(rng); return BAD_FUNC_ARG; + } - if (rng->status != DRBG_OK) + 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 +2924,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 +2972,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 +3036,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..d6ed9484b48 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,25 @@ static WC_DECLARE_INIT_STATE(wolfcrypt_init_state); int aarch64_use_sb = 0; #endif +#ifdef WC_RNG_LOCK_ATFORK +#if !defined(RTLD_NOLOAD) || !defined(RTLD_NODELETE) + #error "WC_RNG_ATFORK needs RTLD_NOLOAD and RTLD_NODELETE" +#endif +/* The fork handlers can never be unregistered, so the image that holds them + * is pinned against dlclose() before they are registered. */ +WOLFSSL_LOCAL void wc_RngPinImage(void* fn) +{ + Dl_info info; + const char* name; /* a pointer on most libcs, an array on Cygwin */ + if (dladdr(fn, &info) == 0 || (name = info.dli_fname) == NULL || + name[0] == '\0' || + dlopen(name, RTLD_NOLOAD | RTLD_NODELETE | RTLD_LAZY) == NULL) { + /* no dlopen() handle means no dlclose() can reach this image */ + WOLFSSL_MSG("RNG fork handlers: no dlopen handle, nothing to pin"); + } +} +#endif + /* Used to initialize state for wolfcrypt return 0 on success */ @@ -555,6 +581,14 @@ int wolfCrypt_Init(void) WOLFCRYPT_INIT_RAISE_BAD_STATE(); } #endif + #ifdef WC_RNG_LOCK_ATFORK + /* here, before the app has threads, so no fork can race it */ + ret = wc_RngAtForkInit(); + if (ret != 0) { + WOLFSSL_MSG("RNG fork handler registration failed"); + 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 68e63525e5c..20a738698bd 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -179,6 +179,14 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #include "wolfcrypt/test/test.h" #endif +#ifdef WC_TEST_RNG_FORK + #include + #include + #include + #include + #include +#endif + /* printf mappings */ #ifndef WOLFSSL_LOG_PRINTF #if defined(FREESCALE_MQX) || defined(FREESCALE_KSDK_MQX) @@ -934,6 +942,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 @@ -2570,6 +2581,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); @@ -26873,6 +26890,22 @@ static wc_test_ret_t random_rng_test(void) return ret; } +/* Freeing a zeroed WC_RNG must be a no-op. Not on Versal (wc_FreeRng resets + * its TRNG) nor where the DRBG is embedded and too big for this stack. */ +#if !defined(WOLFSSL_XILINX_CRYPT_VERSAL) && \ + !(defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_STATIC_MEMORY)) +static wc_test_ret_t rng_zeroed_free_test(void) +{ + WC_RNG zeroed; + int ret; + XMEMSET(&zeroed, 0, sizeof(zeroed)); + ret = wc_FreeRng(&zeroed); + return ret == 0 ? 0 : WC_TEST_RET_ENC_EC(ret); +} +#else +#define rng_zeroed_free_test() ((wc_test_ret_t)0) +#endif + #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ !defined(HAVE_INTEL_RDRAND) @@ -27082,6 +27115,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)); @@ -27298,12 +27335,457 @@ 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 + +/* Elapsed nanoseconds, capped at two seconds so nothing overflows. */ +static long rng_test_elapsed_ns(const struct timespec* a, + const struct timespec* b) +{ + if (b->tv_sec - a->tv_sec >= 2) + return 2000000000L; + return (b->tv_sec - a->tv_sec) * 1000000000L + (b->tv_nsec - a->tv_nsec); +} + +struct rng_fork_holder_args { + WC_RNG* rng; + int fd; /* gets one byte once the lock is held */ + int rfd; /* the go byte arrives here; the hold is timed from it */ +}; + +/* Holds the lock while the other thread enters fork(), as a generate in + * flight would, so the prepare handler must wait for it. It works instead + * of sleeping, as a generate does: a thread checker's sleep hook needs a lock + * its fork hook holds. */ +static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_holder(void* arg) +{ + struct rng_fork_holder_args* a = (struct rng_fork_holder_args*)arg; + struct timespec start, now; + byte held = 1; + byte go = 0; + int rc = -1; + + if (a->rng->lock != NULL) { + do { + rc = sem_wait(&a->rng->lock->sem); + } while (rc != 0 && errno == EINTR); + } + if (rc == 0) { + /* hold for the full time only once the tester says it is timing */ + if (write(a->fd, &held, 1) == 1 && read(a->rfd, &go, 1) == 1 && + clock_gettime(CLOCK_MONOTONIC, &start) == 0) { + do { + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + break; + } while (rng_test_elapsed_ns(&start, &now) < WC_RNG_FORK_HOLD_NS); + } + (void)sem_post(&a->rng->lock->sem); + } + close(a->fd); /* EOF if the lock was never held */ + close(a->rfd); + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* fork() while another thread holds the lock: the child must finish with a + * different next block. The hold is best effort; the checks hold anyway. */ +static wc_test_ret_t rng_fork_test(WC_RNG* rng) +{ + WC_DECLARE_VAR(parent, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + WC_DECLARE_VAR(child, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + 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 gfd[2]; + int piped = 0; + int started = 0; + pid_t pid = -1; + int status = 0; + byte held = 0; + byte go = 1; + struct timespec t0, t1; + + WC_ALLOC_VAR(parent, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + WC_ALLOC_VAR(child, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + 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; + if (pipe(gfd) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + piped = 3; + + h->rng = rng; + h->fd = hfd[1]; + h->rfd = gfd[0]; + if (wolfSSL_NewThread(&holder, &rng_fork_test_holder, h) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + started = 1; + if (read(hfd[0], &held, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + + (void)clock_gettime(CLOCK_MONOTONIC, &t0); /* before the go byte */ + if (write(gfd[1], &go, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + pid = fork(); + (void)clock_gettime(CLOCK_MONOTONIC, &t1); + if (pid == 0) { + if (wc_RNG_GenerateBlock(rng, child, WC_RNG_THREAD_TEST_BLKSZ) != 0) + _exit(1); + 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); + /* prepare had to wait for the holder, so fork() took most of the hold */ + if (rng_test_elapsed_ns(&t0, &t1) < WC_RNG_FORK_HOLD_NS / 2) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + + if (read(fd[0], child, WC_RNG_THREAD_TEST_BLKSZ) != + (ssize_t)WC_RNG_THREAD_TEST_BLKSZ) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + if (waitpid(pid, &status, 0) != pid) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + pid = -1; + if ((! WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + + ret = wc_RNG_GenerateBlock(rng, parent, WC_RNG_THREAD_TEST_BLKSZ); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), done); + /* Both draws follow the fork, so a match means the child kept the + * parent's state; with HAVE_GETPID the pid check reseeds it too. */ + if (XMEMCMP(parent, child, WC_RNG_THREAD_TEST_BLKSZ) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + +done: + if (pid > 0) { + (void)kill(pid, SIGKILL); + (void)waitpid(pid, NULL, 0); + } + if (piped >= 3) + close(gfd[1]); /* EOF frees a holder still waiting for go */ + if (started && (wolfSSL_JoinThread(holder) != 0) && ret == 0) + ret = WC_TEST_RET_ENC_NC; + if (piped >= 1) { + if (fd[0] >= 0) + close(fd[0]); + if (fd[1] >= 0) + close(fd[1]); + } + if (piped >= 2) { + close(hfd[0]); + if (!started) + close(hfd[1]); + } + if (piped >= 3 && !started) + close(gfd[0]); + XFREE(h, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR(parent, HEAP_HINT); + WC_FREE_VAR(child, HEAP_HINT); + return ret; +} +/* A generate on a held instance must not finish until the holder lets go. */ +static wc_test_ret_t rng_lock_wait_test(WC_RNG* rng) +{ + WC_DECLARE_VAR(blk, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + struct rng_fork_holder_args* h = NULL; + THREAD_TYPE holder = INVALID_THREAD_VAL; + struct timespec t0, t1; + wc_test_ret_t ret = 0; + int hfd[2] = { -1, -1 }; + int gfd[2] = { -1, -1 }; + int started = 0; + byte held = 0; + byte go = 1; + + WC_ALLOC_VAR(blk, byte, WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT); + h = (struct rng_fork_holder_args*)XMALLOC(sizeof(*h), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if ((! WC_VAR_OK(blk)) || h == NULL || pipe(hfd) != 0 || pipe(gfd) != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), done); + h->rng = rng; + h->fd = hfd[1]; + h->rfd = gfd[0]; + if (wolfSSL_NewThread(&holder, &rng_fork_test_holder, h) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + started = 1; + if (read(hfd[0], &held, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + (void)clock_gettime(CLOCK_MONOTONIC, &t0); /* before the go byte */ + if (write(gfd[1], &go, 1) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); + ret = wc_RNG_GenerateBlock(rng, blk, WC_RNG_THREAD_TEST_BLKSZ); + (void)clock_gettime(CLOCK_MONOTONIC, &t1); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), done); + if (rng_test_elapsed_ns(&t0, &t1) < WC_RNG_FORK_HOLD_NS / 2) + ERROR_OUT(WC_TEST_RET_ENC_NC, done); /* it did not wait */ + +done: + if (gfd[1] >= 0) + close(gfd[1]); /* EOF frees a holder still waiting for go */ + if (started && (wolfSSL_JoinThread(holder) != 0) && ret == 0) + ret = WC_TEST_RET_ENC_NC; + if (hfd[0] >= 0) + close(hfd[0]); + if (!started) { + if (hfd[1] >= 0) + close(hfd[1]); + if (gfd[0] >= 0) + close(gfd[0]); + } + XFREE(h, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + WC_FREE_VAR(blk, HEAP_HINT); + return ret; +} + +struct rng_churn_args { + int ret; /* first failure, if any */ + long ns; /* how long to keep registering and freeing */ +}; + +/* Registers and frees instances while the fork tests run, so the registry + * changes under the handlers. */ +static THREAD_RETURN WOLFSSL_THREAD rng_fork_test_churn(void* arg) +{ + struct rng_churn_args* a = (struct rng_churn_args*)arg; + struct timespec start, now; + WC_RNG* r; + + if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) + WOLFSSL_RETURN_FROM_THREAD(0); + do { + r = NULL; + a->ret = wc_rng_new_ex(&r, NULL, 0, HEAP_HINT, INVALID_DEVID); + if (a->ret != 0) + break; + wc_rng_free(r); + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + break; + } while (rng_test_elapsed_ns(&start, &now) < a->ns); + WOLFSSL_RETURN_FROM_THREAD(0); +} + +#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 started = 0; + int nblocks; + int i, j; + wc_test_ret_t ret = 0; + + WOLFSSL_ENTER("random_thread_test"); + + 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; + struct rng_churn_args* c = NULL; + THREAD_TYPE churn = INVALID_THREAD_VAL; /* joined only if started */ + int churning = 0; + (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_lock_wait_test(rng); + if (ret != 0) { + wc_rng_free(third); + goto out_free; + } + c = (struct rng_churn_args*)XMALLOC(sizeof(*c), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (c == NULL) { + wc_rng_free(third); + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_free); + } + c->ret = 0; + c->ns = 6 * WC_RNG_FORK_HOLD_NS; /* outlasts both fork tests */ + if (wolfSSL_NewThread(&churn, &rng_fork_test_churn, c) == 0) + churning = 1; + ret = rng_fork_test(rng); + if (ret == 0) + ret = rng_fork_test(third); + if (churning && wolfSSL_JoinThread(churn) != 0 && ret == 0) + ret = WC_TEST_RET_ENC_NC; + else if (ret == 0 && (!churning || c->ret != 0)) + ret = churning ? WC_TEST_RET_ENC_EC(c->ret) : WC_TEST_RET_ENC_NC; + XFREE(c, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_rng_free(third); + if (ret != 0) + goto out_free; + } +#endif /* WC_TEST_RNG_FORK */ + +#ifdef WC_RNG_LOCK_ATFORK + /* A lock marked broken fails closed. */ + { + byte seed[16]; + XMEMSET(seed, 0xa5, sizeof(seed)); + rng->lock->broken = 1; + ret = wc_RNG_GenerateBlock(rng, out, WC_RNG_THREAD_TEST_BLKSZ); + if (ret != WC_NO_ERR_TRACE(BAD_MUTEX_E)) { + rng->lock->broken = 0; + ERROR_OUT(ret == 0 ? WC_TEST_RET_ENC_NC : WC_TEST_RET_ENC_EC(ret), + out_free); + } + ret = wc_RNG_DRBG_Reseed(rng, seed, (word32)sizeof(seed)); + rng->lock->broken = 0; + if (ret != WC_NO_ERR_TRACE(BAD_MUTEX_E)) { + ERROR_OUT(ret == 0 ? WC_TEST_RET_ENC_NC : WC_TEST_RET_ENC_EC(ret), + out_free); + } + ret = 0; + } +#endif /* WC_RNG_LOCK_ATFORK */ + + 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) + ret = WC_TEST_RET_ENC_NC; + } + if (ret != 0) + goto 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); + } + if (rng->status != WC_DRBG_OK) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); + + /* Fewer than two threads tested nothing. */ + if (started < 2) + ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); + + /* All pairs, as a smoke test. */ + nblocks = started * WC_RNG_THREAD_TEST_DRAWS; + for (i = 1; i < nblocks; i++) { + for (j = 0; j < i; j++) { + if (XMEMCMP(out + ((size_t)i * WC_RNG_THREAD_TEST_BLKSZ), + out + ((size_t)j * WC_RNG_THREAD_TEST_BLKSZ), + WC_RNG_THREAD_TEST_BLKSZ) == 0) { + ERROR_OUT(WC_TEST_RET_ENC_NC, out_free); + } + } + } + +out_free: + if (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..0c26a78c4a8 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -24,6 +24,10 @@ #define WOLFCRYPT_TEST_H #include +#ifndef WC_NO_RNG + /* for WC_RNG_HAVE_LOCK and WC_RNG_LOCK_ATFORK; above extern "C" */ + #include +#endif #ifdef __cplusplus extern "C" { @@ -39,6 +43,22 @@ #include +/* 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(__STRICT_ANSI__) && (defined(__unix__) || defined(__linux__)) + #define WC_TEST_RNG_FORK +#endif + #ifdef HAVE_STACK_SIZE THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args); #else @@ -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..0bf3a2cca52 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -34,6 +34,44 @@ #include #endif /* HAVE_FIPS_VERSION >= 2 */ +/* make sure Hash DRBG is enabled, unless WC_NO_HASHDRBG is defined + or CUSTOM_RAND_GENERATE_BLOCK is defined */ +#if !defined(WC_NO_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) + #undef HAVE_HASHDRBG + #define HAVE_HASHDRBG + #ifndef WC_RESEED_INTERVAL + #define WC_RESEED_INTERVAL (1000000) + #endif +#endif + +/* One lock per WC_RNG so threads can share it. WC_RNG_NO_LOCK opts out; + * kernel modules have their own lock-free design. Bank builds keep it: they + * still hand out plain instances. */ +#if !defined(WC_RNG_NO_LOCK) && !defined(SINGLE_THREADED) && \ + !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_BSDKM) && \ + defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + #define WC_RNG_HAVE_LOCK +#endif + +/* pthread_atfork handlers so a forked child can keep using its WC_RNG. + * configure and CMake define WC_RNG_ATFORK where the dlclose pin, unnamed + * semaphores and thread cancellation exist; builds whose locks the handlers + * cannot cover are left out. */ +#if defined(WC_RNG_HAVE_LOCK) && defined(WOLFSSL_PTHREADS) && \ + defined(WC_RNG_ATFORK) && !defined(__APPLE__) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(HAVE_ENTROPY_MEMUSE) && \ + !defined(WC_RNG_BANK_SUPPORT) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(HAVE_WNR) && !defined(WOLFSSL_CHECK_MEM_ZERO) && \ + !defined(WOLFSSL_TRACK_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) + #define WC_RNG_LOCK_ATFORK +#endif + +#ifdef WC_RNG_LOCK_ATFORK + #include /* outside extern "C" */ +#endif + #ifdef __cplusplus extern "C" { #endif @@ -67,14 +105,22 @@ #define CUSTOM_RAND_TYPE byte #endif -/* make sure Hash DRBG is enabled, unless WC_NO_HASHDRBG is defined - or CUSTOM_RAND_GENERATE_BLOCK is defined */ -#if !defined(WC_NO_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) - #undef HAVE_HASHDRBG - #define HAVE_HASHDRBG - #ifndef WC_RESEED_INTERVAL - #define WC_RESEED_INTERVAL (1000000) - #endif +#ifdef WC_RNG_LOCK_ATFORK +/* On the heap so a memset of the WC_RNG cannot break the fork registry. An + * unnamed semaphore: fork() copies it and sem_post() is the one unlock a + * child handler may call. macOS has only named ones, so no handlers there. */ +typedef struct WC_RNG_LOCK { + sem_t sem; + void* heap; + struct WC_RNG_LOCK* next; + struct WC_RNG_LOCK** prev; /* the link that leads here */ + void* drbg; /* states a fork child must reseed */ + void* drbg512; + int broken; /* fails closed; set only by a lone child or on a dead sem */ + int cancel; /* the holder's cancel state, back on exit */ +} WC_RNG_LOCK; +WOLFSSL_LOCAL int wc_RngAtForkInit(void); /* from wolfCrypt_Init */ +WOLFSSL_LOCAL void wc_RngPinImage(void* fn); /* keeps fn's image mapped */ #endif @@ -426,6 +472,16 @@ struct WC_RNG { #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) int devId; #endif +#ifdef WC_RNG_LOCK_ATFORK + /* NULL until wc_InitRng succeeds. Initialize only a new or freed WC_RNG: + * wc_InitRng over a live one leaks this and grows the fork registry that + * every fork() walks. */ + WC_RNG_LOCK* lock; +#elif defined(WC_RNG_HAVE_LOCK) + wolfSSL_Mutex lock; /* serializes generate and reseed */ + byte lockInited; /* nonzero once lock exists */ + int lockCancel; /* the holder's cancel state, back on exit */ +#endif }; #endif /* NO FIPS or have FIPS v2*/ @@ -450,6 +506,9 @@ WOLFSSL_ABI WOLFSSL_API WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap); WOLFSSL_API int wc_rng_new_ex(WC_RNG **rng, byte* nonce, word32 nonceSz, void* heap, int devId); +/* wc_rng_new*, wc_InitRng*, wc_InitRng_BankRef, wc_FreeRng and wc_rng_free + * do not take the instance lock: no other thread may use the instance across + * them. */ WOLFSSL_ABI WOLFSSL_API void wc_rng_free(WC_RNG* rng);