Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .wolfssl_known_macro_extras
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,7 @@ fipsCastStatus_get
noinline
ssize_t
sun
taskYIELD
versal
wc_Des3_SetKey
wc_Tls13_HKDF_Expand_Label
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
2 changes: 2 additions & 0 deletions cmake/options.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
7 changes: 7 additions & 0 deletions doc/dox_comments/header_files/random.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
91 changes: 91 additions & 0 deletions wolfcrypt/src/random.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}

Expand Down
134 changes: 134 additions & 0 deletions wolfcrypt/test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -26811,6 +26820,131 @@ 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 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 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].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]);

/* 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++) {
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. */
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;
Expand Down
Loading
Loading