From af9ccbacdfe06f896a4dad07da8141f4ec5f9a18 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 19 Aug 2026 15:32:38 -0600 Subject: [PATCH 1/5] drbg: add thread-safe option, --disable-threadsafe-drbg to opt out --- CMakeLists.txt | 8 +++ cmake/options.h.in | 2 + configure.ac | 18 ++++++ wolfcrypt/src/random.c | 91 ++++++++++++++++++++++++++ wolfcrypt/test/test.c | 128 +++++++++++++++++++++++++++++++++++++ wolfcrypt/test/test.h | 7 ++ wolfssl/wolfcrypt/random.h | 33 ++++++++++ 7 files changed, 287 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f35291e03d2..4a8136165f9 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 c7ecfd7c4c6..7bed3c04d63 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 3b997f801ba..c9d0a793f16 100644 --- a/configure.ac +++ b/configure.ac @@ -2721,6 +2721,24 @@ 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. +AC_ARG_ENABLE([threadsafe-drbg], + [AS_HELP_STRING([--enable-threadsafe-drbg],[Enable sharing one WC_RNG between threads (default: enabled)])], + [ ENABLED_THREADSAFE_DRBG=$enableval ], + [ ENABLED_THREADSAFE_DRBG=yes ] + ) + +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/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index a600356d9e4..6bad0e9e4ab 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -686,9 +686,44 @@ 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. A compare-exchange flag + * rather than a lock, so it is legal in every context the DRBG runs in and + * contention waits with WC_RELAX_LONG_LOOP(). Returns 1 when this call took + * the flag and the caller must release it. */ +static int RngExclEnter(WC_RNG* rng) +{ + WC_ATOMIC_INT_ARG expected = WC_RNG_EXCL_FREE; + + if (WOLFSSL_ATOMIC_LOAD(rng->excl) == WC_RNG_EXCL_OWNER) { + return 0; + } + + while (! wolfSSL_Atomic_Int_CompareExchange(&rng->excl, &expected, + WC_RNG_EXCL_HELD)) + { + expected = WC_RNG_EXCL_FREE; + WC_RELAX_LONG_LOOP(); + } + + 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; } @@ -704,8 +739,19 @@ 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); + 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 @@ -719,8 +765,19 @@ 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); + 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 @@ -1913,6 +1970,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 @@ -2525,6 +2583,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; @@ -2583,12 +2644,31 @@ 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); + + /* 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; } } @@ -2636,6 +2716,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 */ @@ -2791,6 +2877,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 0556fd53513..1c326218494 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); +#ifndef WC_NO_DRBG_THREAD_SAFE +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"); +#ifndef WC_NO_DRBG_THREAD_SAFE + 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); @@ -26811,6 +26820,125 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) #endif /* !HAVE_HASHDRBG || CUSTOM_RAND_GENERATE_BLOCK || HAVE_INTEL_RDRAND */ +#ifndef WC_NO_DRBG_THREAD_SAFE + +/* Exercises the thread-safe DRBG: concurrent draws from one shared WC_RNG. + * Skipped by a build that elects WC_NO_DRBG_THREAD_SAFE, along with the + * feature itself. */ + +#ifndef WC_RNG_THREAD_TEST_THREADS + #define WC_RNG_THREAD_TEST_THREADS 8 +#endif +#ifndef WC_RNG_THREAD_TEST_DRAWS + #define WC_RNG_THREAD_TEST_DRAWS 128 +#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 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; + } + } + + 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 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) + return WC_TEST_RET_ENC_EC(MEMORY_E); + + 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].ret = 0; + if (wolfSSL_NewThread(&threads[i], &rng_thread_test_worker, + &args[i]) != 0) { + ERROR_OUT(WC_TEST_RET_ENC_NC, out_join); + } + started++; + } + +out_join: + + for (i = 0; i < started; i++) + (void)wolfSSL_JoinThread(threads[i]); + + if (ret != 0) + goto out_free; + + for (i = 0; i < started; i++) { + if (args[i].ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(args[i].ret), out_free); + } + + /* All-pairs rather than a sort: no XQSORT dependency, and the block count + * makes the quadratic scan negligible. */ + for (i = 1; i < WC_RNG_THREAD_TEST_BLOCKS; 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_NO_DRBG_THREAD_SAFE */ + #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 ab1f22e85b7..75bff32a773 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -38,6 +38,10 @@ #include #include +#ifndef WC_NO_RNG + /* for WC_NO_DRBG_THREAD_SAFE, which random.h may elect on. */ + #include +#endif #ifdef HAVE_STACK_SIZE THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args); @@ -251,6 +255,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); +#ifndef WC_NO_DRBG_THREAD_SAFE +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..27312b27ca0 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -357,6 +357,33 @@ 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. */ + +/* A build with no DRBG, no atomics, or no threads has nothing to implement + * this with, so elect it off here rather than making every use site restate + * the requirements. */ +#if (!defined(HAVE_HASHDRBG) || defined(CUSTOM_RAND_GENERATE_BLOCK) || \ + defined(SINGLE_THREADED) || defined(WOLFSSL_NO_ATOMICS)) && \ + !defined(WC_NO_DRBG_THREAD_SAFE) + #define WC_NO_DRBG_THREAD_SAFE +#endif + +#ifndef WC_NO_DRBG_THREAD_SAFE + #define WC_RNG_EXCL_FREE 0 + #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. */ + #define WC_RNG_EXCL_OWNER 2 +#endif + /* RNG context */ struct WC_RNG { struct OS_Seed seed; @@ -417,6 +444,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 From cf3e63d5072c1f6d2bd72258dc7e435f0503b215 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 19 Aug 2026 15:36:19 -0600 Subject: [PATCH 2/5] update dox --- doc/dox_comments/header_files/random.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index fa870a927e4..12f7fe994cd 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; From 3c349c1d79ca1a135b492ca0df12fe7d7ca90fdd Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 19 Aug 2026 17:13:10 -0600 Subject: [PATCH 3/5] Portability and CI fixes --- wolfcrypt/test/test.c | 36 +++++++++++++++++++++--------------- wolfcrypt/test/test.h | 22 +++++++++++++++++++++- wolfssl/wolfcrypt/random.h | 23 ++++++++++++++--------- wolfssl/wolfcrypt/types.h | 30 +++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 26 deletions(-) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 1c326218494..94dede06db0 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -925,7 +925,7 @@ 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); -#ifndef WC_NO_DRBG_THREAD_SAFE +#ifdef WC_TEST_THREADSAFE_DRBG WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void); #endif #ifdef WC_RNG_BANK_SUPPORT @@ -2561,7 +2561,7 @@ 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"); -#ifndef WC_NO_DRBG_THREAD_SAFE +#ifdef WC_TEST_THREADSAFE_DRBG if ((ret = random_thread_test()) != 0) TEST_FAIL("RNGTHRD test failed!\n", ret); else @@ -26820,17 +26820,17 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) #endif /* !HAVE_HASHDRBG || CUSTOM_RAND_GENERATE_BLOCK || HAVE_INTEL_RDRAND */ -#ifndef WC_NO_DRBG_THREAD_SAFE +#ifdef WC_TEST_THREADSAFE_DRBG /* Exercises the thread-safe DRBG: concurrent draws from one shared WC_RNG. - * Skipped by a build that elects WC_NO_DRBG_THREAD_SAFE, along with the - * feature itself. */ + * 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 8 + #define WC_RNG_THREAD_TEST_THREADS 4 #endif #ifndef WC_RNG_THREAD_TEST_DRAWS - #define WC_RNG_THREAD_TEST_DRAWS 128 + #define WC_RNG_THREAD_TEST_DRAWS 96 #endif #ifndef WC_RNG_THREAD_TEST_BLKSZ #define WC_RNG_THREAD_TEST_BLKSZ 32 @@ -26871,6 +26871,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) byte* out = NULL; int rng_inited = 0; int started = 0; + int nblocks; int i, j; wc_test_ret_t ret; @@ -26879,8 +26880,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) out = (byte*)XMALLOC((size_t)WC_RNG_THREAD_TEST_BLOCKS * WC_RNG_THREAD_TEST_BLKSZ, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (out == NULL) - return WC_TEST_RET_ENC_EC(MEMORY_E); + 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) @@ -26894,17 +26898,18 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) args[i].ret = 0; if (wolfSSL_NewThread(&threads[i], &rng_thread_test_worker, &args[i]) != 0) { - ERROR_OUT(WC_TEST_RET_ENC_NC, out_join); + /* Out of thread resources; run with the ones we have. */ + break; } started++; } -out_join: - for (i = 0; i < started; i++) (void)wolfSSL_JoinThread(threads[i]); - if (ret != 0) + /* 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; for (i = 0; i < started; i++) { @@ -26914,7 +26919,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) /* All-pairs rather than a sort: no XQSORT dependency, and the block count * makes the quadratic scan negligible. */ - for (i = 1; i < WC_RNG_THREAD_TEST_BLOCKS; i++) { + 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), @@ -26937,7 +26943,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void) return ret; } -#endif /* !WC_NO_DRBG_THREAD_SAFE */ +#endif /* WC_TEST_THREADSAFE_DRBG */ #ifdef WC_RNG_BANK_SUPPORT diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index 75bff32a773..19b3f4788e5 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -43,6 +43,26 @@ #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; + * - 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 + * build checks out locked copies of both from the module's tag, and those + * predate it, so HAVE_FIPS is excluded outright. Note the locked + * random.h also never defines WC_NO_DRBG_THREAD_SAFE, so the test above + * cannot detect this on its own. */ +#if !defined(WC_NO_DRBG_THREAD_SAFE) && !defined(HAVE_FIPS) && \ + !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); #else @@ -255,7 +275,7 @@ 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); -#ifndef WC_NO_DRBG_THREAD_SAFE +#ifdef WC_TEST_THREADSAFE_DRBG extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_thread_test(void); #endif #ifdef WC_RNG_BANK_SUPPORT diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 27312b27ca0..02c0922e72f 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -79,6 +79,20 @@ /* avoid redefinition of structs */ +/* A build with no DRBG, no atomics, or no threads 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(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)) @@ -367,15 +381,6 @@ enum wc_RngHealthState { * election, independent of SINGLE_THREADED -- a multi-threaded build that keeps * its WC_RNGs thread-local can opt out and keep the smaller struct. */ -/* A build with no DRBG, no atomics, or no threads has nothing to implement - * this with, so elect it off here rather than making every use site restate - * the requirements. */ -#if (!defined(HAVE_HASHDRBG) || defined(CUSTOM_RAND_GENERATE_BLOCK) || \ - defined(SINGLE_THREADED) || defined(WOLFSSL_NO_ATOMICS)) && \ - !defined(WC_NO_DRBG_THREAD_SAFE) - #define WC_NO_DRBG_THREAD_SAFE -#endif - #ifndef WC_NO_DRBG_THREAD_SAFE #define WC_RNG_EXCL_FREE 0 #define WC_RNG_EXCL_HELD 1 diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 3cce9b4c70b..a37469c8ac2 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -2412,8 +2412,36 @@ WOLFSSL_API word32 CheckRunTimeSettings(void); struct wc_static_assert_dummy_struct #endif +/* Hook run once per iteration of a long wait loop. On a preemptive + * general-purpose OS a bare spin merely wastes cycles, but on a uniprocessor + * RTOS a spinning higher-priority task can starve the lower-priority task it + * is waiting on, so map it to that RTOS's cooperative yield where one is in + * scope. Any port may define WC_RELAX_LONG_LOOP ahead of this. */ #ifndef WC_RELAX_LONG_LOOP - #define WC_RELAX_LONG_LOOP() WC_DO_NOTHING + #if defined(WOLFSSL_ZEPHYR) && !defined(SINGLE_THREADED) + /* is included by wc_port.h whenever + * !SINGLE_THREADED, so k_yield() is declared here. */ + #define WC_RELAX_LONG_LOOP() k_yield() + #elif (defined(FREERTOS) || defined(FREERTOS_TCP) || \ + defined(WOLFSSL_SAFERTOS)) && defined(taskYIELD) + /* Same grouping wc_port.h uses for these three. 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 keeps the no-op. */ + #define WC_RELAX_LONG_LOOP() taskYIELD() + #elif defined(THREADX) + /* is included by wc_port.h for every THREADX build. */ + #define WC_RELAX_LONG_LOOP() tx_thread_relinquish() + #elif defined(WOLFSSL_TIRTOS) + /* is included by wc_port.h for every TIRTOS + * translation unit. */ + #define WC_RELAX_LONG_LOOP() Task_yield() + #elif defined(RTTHREAD) && !defined(SINGLE_THREADED) + /* "rtthread.h" is included by wc_port.h on the multi-threaded path. */ + #define WC_RELAX_LONG_LOOP() rt_thread_yield() + #else + #define WC_RELAX_LONG_LOOP() WC_DO_NOTHING + #endif #endif #ifndef WC_CHECK_FOR_INTR_SIGNALS #define WC_CHECK_FOR_INTR_SIGNALS() 0 From 7fbe9834c69674fe5278d0082e2f1411ffd7f6d1 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 19 Aug 2026 18:26:38 -0600 Subject: [PATCH 4/5] CI fixes --- wolfcrypt/test/test.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index 19b3f4788e5..73882da9bff 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -51,12 +51,13 @@ * assuming !SINGLE_THREADED is enough; * - 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 - * build checks out locked copies of both from the module's tag, and those - * predate it, so HAVE_FIPS is excluded outright. Note the locked - * random.h also never defines WC_NO_DRBG_THREAD_SAFE, so the test above - * cannot detect this on its own. */ -#if !defined(WC_NO_DRBG_THREAD_SAFE) && !defined(HAVE_FIPS) && \ + * - 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_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))) From e82d12f811966a197522f269c63456a1b605a8ec Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 19 Aug 2026 23:43:13 -0600 Subject: [PATCH 5/5] multi-test item --- .wolfssl_known_macro_extras | 1 + 1 file changed, 1 insertion(+) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 5edebd83ef1..aa967304dd0 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -1356,6 +1356,7 @@ fipsCastStatus_get noinline ssize_t sun +taskYIELD versal wc_Des3_SetKey wc_Tls13_HKDF_Expand_Label