diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 4f88a806c0..4235e8652e 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -782,6 +782,7 @@ WC_PUF_HELPER_COMPACT WC_PUF_SHA3 WC_RNG_BANK_NO_DEFAULT_SUPPORT WC_RNG_BLOCKING +WC_RNG_EXCL_TIMEOUT_SEC WC_RSA_NONBLOCK_TIME WC_RSA_NO_FERMAT_CHECK WC_RTL8735B_NO_DERIVE_CACHE @@ -1361,6 +1362,7 @@ fipsCastStatus_get noinline ssize_t sun +taskYIELD versal wc_Des3_SetKey wc_Tls13_HKDF_Expand_Label diff --git a/CMakeLists.txt b/CMakeLists.txt index 08ae2c8b8a..9c56f27d60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3688,6 +3688,14 @@ if(WOLFSSL_RNG_BANK) list(APPEND WOLFSSL_DEFINITIONS "-DWC_RNG_BANK_SUPPORT") endif() +# Thread-safe DRBG (share one WC_RNG between threads) +add_option("WOLFSSL_THREADSAFE_DRBG" + "Enable sharing one WC_RNG between threads (default: enabled)" + "yes" "yes;no") +if(NOT WOLFSSL_THREADSAFE_DRBG) + list(APPEND WOLFSSL_DEFINITIONS "-DWC_NO_DRBG_THREAD_SAFE") +endif() + # Valgrind (for unit tests) add_option("WOLFSSL_VALGRIND" "Enable valgrind for unit tests (default: disabled)" diff --git a/cmake/options.h.in b/cmake/options.h.in index c7ecfd7c4c..7bed3c04d6 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -597,6 +597,8 @@ extern "C" { #cmakedefine FP_ECC #undef WC_RNG_BANK_SUPPORT #cmakedefine WC_RNG_BANK_SUPPORT +#undef WC_NO_DRBG_THREAD_SAFE +#cmakedefine WC_NO_DRBG_THREAD_SAFE #undef HAVE_VALGRIND #cmakedefine HAVE_VALGRIND #undef HAVE_CRL_MONITOR diff --git a/configure.ac b/configure.ac index 48993b57ed..4886c34cc6 100644 --- a/configure.ac +++ b/configure.ac @@ -2721,6 +2721,33 @@ then AM_CFLAGS="$AM_CFLAGS -DWC_RNG_BANK_SUPPORT" fi +# Thread-safe DRBG. Serializes the generate/reseed path of a single WC_RNG so +# one instance can be shared between threads. Needs real atomics, so a +# single-threaded build elects it off in random.h regardless of this setting. +# +# Default off for --enable-linuxkm: the kernel module reaches the DRBG through +# the RNG bank, which checks out an instance exclusively, so the per-generate +# atomic is redundant there and its wait has no place in an atomic context. +# Enable it explicitly with --enable-threadsafe-drbg. +AC_ARG_ENABLE([threadsafe-drbg], + [AS_HELP_STRING([--enable-threadsafe-drbg],[Enable sharing one WC_RNG between threads (default: enabled, except linuxkm)])], + [ ENABLED_THREADSAFE_DRBG=$enableval ], + [ if test "$ENABLED_LINUXKM" = "no"; then + ENABLED_THREADSAFE_DRBG=yes + else + ENABLED_THREADSAFE_DRBG=no + fi ] + ) + +if test "$ENABLED_THREADSAFE_DRBG" = "no" +then + AM_CFLAGS="$AM_CFLAGS -DWC_NO_DRBG_THREAD_SAFE" +elif test "x$enable_threadsafe_drbg" = "xyes" && \ + test "$ENABLED_SINGLETHREADED" = "yes" +then + AC_MSG_ERROR([--enable-threadsafe-drbg is incompatible with --enable-singlethreaded.]) +fi + # DTLS-SCTP AC_ARG_ENABLE([sctp], diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index fa870a927e..12f7fe994c 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -113,6 +113,13 @@ int wc_InitRng(WC_RNG* rng); \param output buffer to which the block is copied \param sz size of output in bytes + \note One WC_RNG may be shared between threads: the DRBG generate and + reseed path is serialized per instance. Build with + --disable-threadsafe-drbg (WC_NO_DRBG_THREAD_SAFE) to opt out where each + instance is only used by one thread at a time. This covers generate and + reseed only -- wc_InitRng() and wc_FreeRng() must not run concurrently + with a generate on the same instance. + _Example_ \code RNG rng; diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index ea778c402c..158d7ebd0f 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -687,9 +687,94 @@ static int Hash_DRBG_Reseed(DRBG_internal* drbg, const byte* seed, word32 seedSz * and array_add_one (shared utility) which both must * remain available to SHA-512-only builds */ +#ifndef WC_NO_DRBG_THREAD_SAFE +/* Thread-safe DRBG support. Serializes this instance's generate and reseed + * path so one WC_RNG can be shared between threads; it is not a lock discipline + * and makes no claim about atomic-context callers. + * + * The wait is bounded the way the rng_bank spins are: it breaks out on an + * interrupting signal, and on WC_RNG_EXCL_TIMEOUT_SEC when that is defined. + * No timeout is applied by default -- the holder can legitimately be blocked + * in wc_GenerateSeed() for as long as the OS entropy source takes, and failing + * a generate on a slow entropy read would be worse than waiting for it. + * + * Returns 1 when this call took the flag and the caller must release it, 0 when + * exclusivity comes from the owner, or a negative error code. */ +static int RngExclEnter(WC_RNG* rng) +{ +#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) + /* The lock word carries the owner's pid, so a hold inherited through + * fork() (the parent's pid, which no thread here can match) is told apart + * from a live hold by this process without a second variable to race + * against. */ + WC_ATOMIC_INT_ARG self = (WC_ATOMIC_INT_ARG)getpid(); +#else + WC_ATOMIC_INT_ARG self = WC_RNG_EXCL_HELD; +#endif + WC_ATOMIC_INT_ARG expected = WC_RNG_EXCL_FREE; +#ifdef WC_RNG_EXCL_TIMEOUT_SEC + time_t ts1 = XTIME(0); +#endif + + if (WOLFSSL_ATOMIC_LOAD(rng->excl) == WC_RNG_EXCL_OWNER) { + return 0; + } + + while (! wolfSSL_Atomic_Int_CompareExchange(&rng->excl, &expected, self)) + { + int intr_ret; + + #if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) + /* A failed exchange leaves the observed value in expected. Reclaim + * only a hold stamped with a different pid, and only by exchanging + * from that exact value, so a live holder -- including a sibling + * thread of this process, which stamps this same pid -- is never + * displaced. */ + if ((expected != WC_RNG_EXCL_FREE) && + (expected != WC_RNG_EXCL_OWNER) && + (expected != self)) + { + if (wolfSSL_Atomic_Int_CompareExchange(&rng->excl, &expected, + self)) + { + return 1; + } + } + #endif + + intr_ret = WC_CHECK_FOR_INTR_SIGNALS(); + if (intr_ret != 0) { + return intr_ret; + } + + #ifdef WC_RNG_EXCL_TIMEOUT_SEC + if (XTIME(0) - ts1 > (time_t)WC_RNG_EXCL_TIMEOUT_SEC) { + return WC_TIMEOUT_E; + } + #endif + + expected = WC_RNG_EXCL_FREE; + WC_SPIN_RELAX(); + } + + return 1; +} + +/* Only ever called by the thread that got 1 from RngExclEnter(). */ +static void RngExclExit(WC_RNG* rng) +{ + WOLFSSL_ATOMIC_STORE(rng->excl, WC_RNG_EXCL_FREE); +} +#endif /* !WC_NO_DRBG_THREAD_SAFE */ + /* Returns: DRBG_SUCCESS and DRBG_FAILURE or BAD_FUNC_ARG on fail */ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) { +#ifndef WC_NO_DRBG_THREAD_SAFE + int ret; + int excl; +#endif + if (rng == NULL || seed == NULL) { return BAD_FUNC_ARG; } @@ -705,8 +790,22 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) #endif return BAD_FUNC_ARG; } +#ifndef WC_NO_DRBG_THREAD_SAFE + /* Serialize against Generate on the same instance. */ + excl = RngExclEnter(rng); + if (excl < 0) { + return excl; + } + ret = Hash_DRBG_Reseed((DRBG_internal *)rng->drbg, seed, seedSz, + NULL, 0); + if (excl) { + RngExclExit(rng); + } + return ret; +#else return Hash_DRBG_Reseed((DRBG_internal *)rng->drbg, seed, seedSz, NULL, 0); +#endif } #endif #ifdef WOLFSSL_DRBG_SHA512 @@ -720,8 +819,22 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) #endif return BAD_FUNC_ARG; } +#ifndef WC_NO_DRBG_THREAD_SAFE + /* Serialize against Generate on the same instance. */ + excl = RngExclEnter(rng); + if (excl < 0) { + return excl; + } + ret = Hash512_DRBG_Reseed((DRBG_SHA512_internal *)rng->drbg512, + seed, seedSz, NULL, 0); + if (excl) { + RngExclExit(rng); + } + return ret; +#else return Hash512_DRBG_Reseed((DRBG_SHA512_internal *)rng->drbg512, seed, seedSz, NULL, 0); +#endif } #endif @@ -1914,6 +2027,7 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, if (nonce == NULL && nonceSz != 0) return BAD_FUNC_ARG; + /* Also initializes rng->excl where the thread-safe DRBG is enabled. */ XMEMSET(rng, 0, sizeof(*rng)); #ifdef WOLFSSL_HEAP_TEST @@ -2526,6 +2640,9 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) #endif { int ret; +#ifndef WC_NO_DRBG_THREAD_SAFE + int excl = 0; +#endif if (rng == NULL || output == NULL) return BAD_FUNC_ARG; @@ -2584,12 +2701,34 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) if (rng->status != DRBG_OK) return RNG_FAILURE_E; +#ifndef WC_NO_DRBG_THREAD_SAFE + /* Serialize the DRBG core for callers sharing this instance; the paths + * above touch no DRBG state and stay outside. */ + excl = RngExclEnter(rng); + if (excl < 0) { + return excl; + } + + /* Re-check: the instance may have changed state while we waited. */ + if (rng->status != DRBG_OK) { + if (excl) { + RngExclExit(rng); + } + return RNG_FAILURE_E; + } +#endif + #if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) if (rng->pid != getpid()) { rng->pid = getpid(); ret = PollAndReSeed(rng); if (ret != DRBG_SUCCESS) { rng->status = DRBG_FAILED; + #ifndef WC_NO_DRBG_THREAD_SAFE + if (excl) { + RngExclExit(rng); + } + #endif return RNG_FAILURE_E; } } @@ -2637,6 +2776,12 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) ret = RNG_FAILURE_E; rng->status = DRBG_FAILED; } + +#ifndef WC_NO_DRBG_THREAD_SAFE + if (excl) { + RngExclExit(rng); + } +#endif #else /* if we get here then there is an RNG configuration error */ @@ -2792,6 +2937,11 @@ int wc_FreeRng(WC_RNG* rng) } #endif +#ifndef WC_NO_DRBG_THREAD_SAFE + /* Last, so a WC_RNG re-instantiated in place does not start out marked. */ + WOLFSSL_ATOMIC_STORE(rng->excl, WC_RNG_EXCL_FREE); +#endif + return ret; } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index ec68a86093..2ff968b08c 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -925,6 +925,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_THREADSAFE_DRBG +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 @@ -2558,6 +2561,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_THREADSAFE_DRBG + 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); @@ -26998,6 +27007,148 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) #endif /* !HAVE_HASHDRBG || CUSTOM_RAND_GENERATE_BLOCK || HAVE_INTEL_RDRAND */ +#ifdef WC_TEST_THREADSAFE_DRBG + +/* Exercises the thread-safe DRBG: concurrent draws from one shared WC_RNG. + * Needs both the feature and the portable thread API, so it is skipped + * where either is absent. */ + +#ifndef WC_RNG_THREAD_TEST_THREADS + #define WC_RNG_THREAD_TEST_THREADS 4 +#endif +#ifndef WC_RNG_THREAD_TEST_DRAWS + #define WC_RNG_THREAD_TEST_DRAWS 96 +#endif +#ifndef WC_RNG_THREAD_TEST_BLKSZ + #define WC_RNG_THREAD_TEST_BLKSZ 32 +#endif + +#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: also drive the reseed side of the exclusion */ + int ret; +}; + +static THREAD_RETURN WOLFSSL_THREAD rng_thread_test_worker(void* argp) +{ + struct rng_thread_test_args* args = (struct rng_thread_test_args*)argp; + int i; + + for (i = 0; i < WC_RNG_THREAD_TEST_DRAWS; i++) { + int ret = wc_RNG_GenerateBlock(args->rng, + args->out + ((size_t)i * WC_RNG_THREAD_TEST_BLKSZ), + WC_RNG_THREAD_TEST_BLKSZ); + if (ret != 0) { + args->ret = ret; + break; + } + + /* One worker also reseeds, so the exclusion in wc_RNG_DRBG_Reseed() + * is covered and runs against the other workers' generates. Output + * must stay unique across the reseed. */ + 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) { + args->ret = ret; + break; + } + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) +{ + THREAD_TYPE threads[WC_RNG_THREAD_TEST_THREADS]; + struct rng_thread_test_args args[WC_RNG_THREAD_TEST_THREADS]; + WC_RNG rng; + byte* out = NULL; + int rng_inited = 0; + int started = 0; + int nblocks; + int i, j; + wc_test_ret_t ret; + + 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); + if (out == NULL) { + /* Opportunistic check: a target too small to hold the buffer is not + * evidence of a DRBG defect, so skip rather than report failure. */ + return 0; + } + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_free); + rng_inited = 1; + + 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 == 0); + args[i].ret = 0; + if (wolfSSL_NewThread(&threads[i], &rng_thread_test_worker, + &args[i]) != 0) { + /* Out of thread resources; run with the ones we have. */ + break; + } + started++; + } + + for (i = 0; i < started; i++) + (void)wolfSSL_JoinThread(threads[i]); + + /* A generate failure is a real failure however many workers ran, so it is + * inspected before the concurrency check below can skip out. */ + 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 workers means nothing ran concurrently, so there was + * nothing for this test to observe. Skip rather than report failure. */ + if (started < 2) + goto out_free; + + /* All-pairs rather than a sort: no XQSORT dependency, and the block count + * makes the quadratic scan negligible. */ + 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_inited) { + int free_ret = wc_FreeRng(&rng); + if ((ret == 0) && (free_ret != 0)) + ret = WC_TEST_RET_ENC_EC(free_ret); + } + + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +#endif /* WC_TEST_THREADSAFE_DRBG */ + #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 ab1f22e85b..41a01e58f6 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -38,6 +38,33 @@ #include #include +#ifndef WC_NO_RNG + /* for WC_NO_DRBG_THREAD_SAFE, which random.h may elect on. */ + #include +#endif + +/* The thread-safe DRBG test drives one instance from several threads, so it + * needs more than the feature itself: + * - wolfSSL_NewThread()/wolfSSL_JoinThread(), which are only implemented for + * a subset of targets (notably not WOLFSSL_LINUXKM, which builds this file + * into the kernel module), so require one that has them rather than + * assuming !SINGLE_THREADED is enough; + * - an RNG at all: WC_NO_RNG both removes wc_FreeRng() and stops test.h + * from including random.h, so WC_NO_DRBG_THREAD_SAFE would not be seen; + * - a general-purpose heap for the comparison buffer, which rules out + * WOLFSSL_NO_MALLOC and WOLFSSL_STATIC_MEMORY; + * - a random.c/random.h pair that actually carries the feature. A FIPS or + * selftest build checks out locked copies of both from the module's tag, + * and those predate it, so HAVE_FIPS and HAVE_SELFTEST are excluded + * outright. Note the locked random.h also never defines + * WC_NO_DRBG_THREAD_SAFE, so the test above cannot detect this itself. */ +#if !defined(WC_NO_RNG) && !defined(WC_NO_DRBG_THREAD_SAFE) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_STATIC_MEMORY) && \ + (defined(WOLFSSL_PTHREADS) || \ + (defined(USE_WINDOWS_API) && !defined(_WIN32_WCE))) + #define WC_TEST_THREADSAFE_DRBG +#endif #ifdef HAVE_STACK_SIZE THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args); @@ -251,6 +278,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_THREADSAFE_DRBG +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 d9c5a9289b..9540387e50 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -79,6 +79,22 @@ /* avoid redefinition of structs */ +/* A build with no DRBG, no atomics, no threads, or no way for the acquire + * spin to yield (WC_SPIN_RELAX_YIELDS, types.h) has nothing to implement this + * with, so elect it off here rather than making every use site restate the + * requirements. Kept ahead of the FIPS-version guard below: the use + * sites test !defined(WC_NO_DRBG_THREAD_SAFE), so this must be evaluated on + * every path that reaches them, including the one where the WC_RNG defined + * below is not the struct in use. */ +#if (!defined(HAVE_HASHDRBG) || defined(CUSTOM_RAND_GENERATE_BLOCK) || \ + defined(SINGLE_THREADED) || defined(WOLFSSL_NO_ATOMICS) || \ + !defined(WC_SPIN_RELAX_YIELDS) || \ + (defined(HAVE_FIPS) && \ + !(defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 2)))) && \ + !defined(WC_NO_DRBG_THREAD_SAFE) + #define WC_NO_DRBG_THREAD_SAFE +#endif + #if !defined(HAVE_FIPS) || \ (defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 2)) @@ -357,6 +373,28 @@ enum wc_RngHealthState { WOLF_ENUM_DUMMY_LAST_ELEMENT(wc_RngHealthState) }; +/* Thread-safe DRBG -- enabled by default. + * + * Serializes the generate and reseed path of a single WC_RNG so that one + * instance can be shared across threads without an external lock of its own. + * + * Define WC_NO_DRBG_THREAD_SAFE to opt out where an instance is only ever used + * by one thread at a time and the per-call atomic is not wanted. This is an + * election, independent of SINGLE_THREADED -- a multi-threaded build that keeps + * its WC_RNGs thread-local can opt out and keep the smaller struct. */ + +#ifndef WC_NO_DRBG_THREAD_SAFE + #define WC_RNG_EXCL_FREE 0 + /* Value stored while held. Where getpid() is available the holder stores + * its pid instead, so a hold inherited through fork() carries the parent's + * pid and is distinguishable from a live one by the lock word alone. */ + #define WC_RNG_EXCL_HELD 1 + /* Stored once by an owner that already supplies exclusivity for this + * instance, which then takes no flag of its own. Nothing here sets it. + * Negative so it can never collide with a pid. */ + #define WC_RNG_EXCL_OWNER (-1) +#endif + /* RNG context */ struct WC_RNG { struct OS_Seed seed; @@ -417,6 +455,12 @@ struct WC_RNG { #endif /* WC_RNG_BANK_SUPPORT || HAVE_HASHDRBG */ +#ifndef WC_NO_DRBG_THREAD_SAFE + /* Serializes this instance's DRBG generate/reseed path. Outside the union + * above, and left FREE by the _InitRng() XMEMSET. */ + wolfSSL_Atomic_Int excl; +#endif + #if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) pid_t pid; #endif diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 3cce9b4c70..9678fc18df 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -2414,6 +2414,66 @@ WOLFSSL_API word32 CheckRunTimeSettings(void); #ifndef WC_RELAX_LONG_LOOP #define WC_RELAX_LONG_LOOP() WC_DO_NOTHING +#else + /* A port supplied a real relax hook of its own (linuxkm does). */ + #define WC_HAVE_PORT_RELAX_LONG_LOOP +#endif + +/* Yield hook for the DRBG acquire spin in RngExclEnter(). Deliberately + * separate from WC_RELAX_LONG_LOOP(), which also backs the + * SAVE_/RESTORE_NO_VECTOR_REGISTERS fallbacks below and must stay a no-op + * there -- those expand at hundreds of crypto call sites, and a scheduler + * yield does not belong in them. + * + * On a preemptive general-purpose OS a bare spin only wastes cycles, but on a + * uniprocessor RTOS a spinning higher-priority task can starve the + * lower-priority task it waits on, so use that RTOS's cooperative yield where + * one is in scope. SINGLE_THREADED is excluded first: wc_port.h omits these + * kernel headers in that configuration, so the yields have no declaration. + * Any port may define WC_SPIN_RELAX ahead of this. */ +/* WC_SPIN_RELAX_YIELDS is defined alongside every mapping that really hands + * the CPU over. Where it is absent the spin cannot yield, and random.h elects + * the thread-safe DRBG off rather than ship a wait that a priority-preemptive + * scheduler can turn into a livelock. */ +#ifdef WC_SPIN_RELAX + /* Supplied by the port; taken to be a real yield. */ + #define WC_SPIN_RELAX_YIELDS +#elif defined(SINGLE_THREADED) + #define WC_SPIN_RELAX() WC_DO_NOTHING +#elif defined(WOLFSSL_ZEPHYR) + #define WC_SPIN_RELAX() k_yield() + #define WC_SPIN_RELAX_YIELDS +#elif (defined(FREERTOS) || defined(FREERTOS_TCP) || \ + defined(WOLFSSL_SAFERTOS)) && defined(taskYIELD) + /* taskYIELD() is a macro from FreeRTOS task.h, which none of these paths + * include themselves, so key off the macro rather than assume it: a build + * without task.h falls through to the no-yield default. */ + #define WC_SPIN_RELAX() taskYIELD() + #define WC_SPIN_RELAX_YIELDS +#elif defined(THREADX) + #define WC_SPIN_RELAX() tx_thread_relinquish() + #define WC_SPIN_RELAX_YIELDS +#elif defined(WOLFSSL_TIRTOS) + #define WC_SPIN_RELAX() Task_yield() + #define WC_SPIN_RELAX_YIELDS +#elif defined(RTTHREAD) + #define WC_SPIN_RELAX() rt_thread_yield() + #define WC_SPIN_RELAX_YIELDS +#elif defined(WOLFSSL_PTHREADS) + /* wc_port.h includes on this path, which carries ; + * wolfentropy.c and async.c already call sched_yield() the same way. */ + #define WC_SPIN_RELAX() (void)sched_yield() + #define WC_SPIN_RELAX_YIELDS +#elif defined(USE_WINDOWS_API) && !defined(_WIN32_WCE) + /* is included by wc_port.h on this path. */ + #define WC_SPIN_RELAX() (void)SwitchToThread() + #define WC_SPIN_RELAX_YIELDS +#elif defined(WC_HAVE_PORT_RELAX_LONG_LOOP) + /* linuxkm and anything else that installed its own relax hook. */ + #define WC_SPIN_RELAX() WC_RELAX_LONG_LOOP() + #define WC_SPIN_RELAX_YIELDS +#else + #define WC_SPIN_RELAX() WC_DO_NOTHING #endif #ifndef WC_CHECK_FOR_INTR_SIGNALS #define WC_CHECK_FOR_INTR_SIGNALS() 0