diff options
author | Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> | 2022-07-21 10:05:00 -0300 |
---|---|---|
committer | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2022-07-22 11:58:27 -0300 |
commit | 8dd890d96f1833a58de6e112a14d63ab44e0a402 (patch) | |
tree | 0369ccc9a88124cc69c83ad51cfc640fac45bb15 /stdlib/tst-arc4random-thread.c | |
parent | 6f4e0fcfa2d2b0915816a3a3a1d48b4763a7dee2 (diff) | |
download | glibc-8dd890d96f1833a58de6e112a14d63ab44e0a402.zip glibc-8dd890d96f1833a58de6e112a14d63ab44e0a402.tar.gz glibc-8dd890d96f1833a58de6e112a14d63ab44e0a402.tar.bz2 |
stdlib: Add arc4random tests
The basic tst-arc4random-chacha20.c checks if the output of ChaCha20
implementation matches the reference test vectors from RFC8439.
The tst-arc4random-fork.c check if subprocesses generate distinct
streams of randomness (if fork handling is done correctly).
The tst-arc4random-stats.c is a statistical test to the randomness of
arc4random, arc4random_buf, and arc4random_uniform.
The tst-arc4random-thread.c check if threads generate distinct streams
of randomness (if function are thread-safe).
Checked on x86_64-linux-gnu, aarch64-linux, and powerpc64le-linux-gnu.
Co-authored-by: Florian Weimer <fweimer@redhat.com>
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Diffstat (limited to 'stdlib/tst-arc4random-thread.c')
-rw-r--r-- | stdlib/tst-arc4random-thread.c | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/stdlib/tst-arc4random-thread.c b/stdlib/tst-arc4random-thread.c new file mode 100644 index 0000000..3373d4d --- /dev/null +++ b/stdlib/tst-arc4random-thread.c @@ -0,0 +1,341 @@ +/* Test that threads generate distinct streams of randomness. + Copyright (C) 2022 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <array_length.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <support/check.h> +#include <support/namespace.h> +#include <support/support.h> +#include <support/xthread.h> + +/* Number of arc4random_buf calls per thread. */ +enum { count_per_thread = 5000 }; + +/* Number of threads computing randomness. */ +enum { inner_threads = 5 }; + +/* Number of threads launching other threads. Chosen as to not to + overload the system. */ +enum { outer_threads = 7 }; + +/* Number of launching rounds performed by the outer threads. */ +enum { outer_rounds = 10 }; + +/* Maximum number of bytes generated in an arc4random call. */ +enum { max_size = 32 }; + +/* Sizes generated by threads. Must be long enough to be unique with + high probability. */ +static const int sizes[] = { 12, 15, 16, 17, 24, 31, max_size }; + +/* Data structure to capture randomness results. */ +struct blob +{ + unsigned int size; + int thread_id; + unsigned int index; + unsigned char bytes[max_size]; +}; + +struct subprocess_args +{ + struct blob *blob; + void (*func)(unsigned char *, size_t); +}; + +static void +generate_arc4random (unsigned char *bytes, size_t size) +{ + int i; + for (i = 0; i < size / sizeof (uint32_t); i++) + { + uint32_t x = arc4random (); + memcpy (&bytes[4 * i], &x, sizeof x); + } + int rem = size % sizeof (uint32_t); + if (rem > 0) + { + uint32_t x = arc4random (); + memcpy (&bytes[4 * i], &x, rem); + } +} + +static void +generate_arc4random_buf (unsigned char *bytes, size_t size) +{ + arc4random_buf (bytes, size); +} + +static void +generate_arc4random_uniform (unsigned char *bytes, size_t size) +{ + for (int i = 0; i < size; i++) + bytes[i] = arc4random_uniform (256); +} + +#define DYNARRAY_STRUCT dynarray_blob +#define DYNARRAY_ELEMENT struct blob +#define DYNARRAY_PREFIX dynarray_blob_ +#include <malloc/dynarray-skeleton.c> + +/* Sort blob elements by length first, then by comparing the data + member. */ +static int +compare_blob (const void *left1, const void *right1) +{ + const struct blob *left = left1; + const struct blob *right = right1; + + if (left->size != right->size) + /* No overflow due to limited range. */ + return left->size - right->size; + return memcmp (left->bytes, right->bytes, left->size); +} + +/* Used to store the global result. */ +static pthread_mutex_t global_result_lock = PTHREAD_MUTEX_INITIALIZER; +static struct dynarray_blob global_result; + +/* Copy data to the global result, with locking. */ +static void +copy_result_to_global (struct dynarray_blob *result) +{ + xpthread_mutex_lock (&global_result_lock); + size_t old_size = dynarray_blob_size (&global_result); + TEST_VERIFY_EXIT + (dynarray_blob_resize (&global_result, + old_size + dynarray_blob_size (result))); + memcpy (dynarray_blob_begin (&global_result) + old_size, + dynarray_blob_begin (result), + dynarray_blob_size (result) * sizeof (struct blob)); + xpthread_mutex_unlock (&global_result_lock); +} + +/* Used to assign unique thread IDs. Accessed atomically. */ +static int next_thread_id; + +static void * +inner_thread (void *closure) +{ + void (*func) (unsigned char *, size_t) = closure; + + /* Use local result to avoid global lock contention while generating + randomness. */ + struct dynarray_blob result; + dynarray_blob_init (&result); + + int thread_id = __atomic_fetch_add (&next_thread_id, 1, __ATOMIC_RELAXED); + + /* Determine the sizes to be used by this thread. */ + int size_slot = thread_id % (array_length (sizes) + 1); + bool switch_sizes = size_slot == array_length (sizes); + if (switch_sizes) + size_slot = 0; + + /* Compute the random blobs. */ + for (int i = 0; i < count_per_thread; ++i) + { + struct blob *place = dynarray_blob_emplace (&result); + TEST_VERIFY_EXIT (place != NULL); + place->size = sizes[size_slot]; + place->thread_id = thread_id; + place->index = i; + func (place->bytes, place->size); + + if (switch_sizes) + size_slot = (size_slot + 1) % array_length (sizes); + } + + /* Store the blobs in the global result structure. */ + copy_result_to_global (&result); + + dynarray_blob_free (&result); + + return NULL; +} + +/* Launch the inner threads and wait for their termination. */ +static void * +outer_thread (void *closure) +{ + void (*func) (unsigned char *, size_t) = closure; + + for (int round = 0; round < outer_rounds; ++round) + { + pthread_t threads[inner_threads]; + + for (int i = 0; i < inner_threads; ++i) + threads[i] = xpthread_create (NULL, inner_thread, func); + + for (int i = 0; i < inner_threads; ++i) + xpthread_join (threads[i]); + } + + return NULL; +} + +static bool termination_requested; + +/* Call arc4random_buf to fill one blob with 16 bytes. */ +static void * +get_one_blob_thread (void *closure) +{ + struct subprocess_args *arg = closure; + struct blob *result = arg->blob; + + result->size = 16; + arg->func (result->bytes, result->size); + return NULL; +} + +/* Invoked from fork_thread to actually obtain randomness data. */ +static void +fork_thread_subprocess (void *closure) +{ + struct subprocess_args *arg = closure; + struct blob *shared_result = arg->blob; + + struct subprocess_args args[3] = + { + { shared_result + 0, arg->func }, + { shared_result + 1, arg->func }, + { shared_result + 2, arg->func } + }; + + pthread_t thr1 = xpthread_create (NULL, get_one_blob_thread, &args[1]); + pthread_t thr2 = xpthread_create (NULL, get_one_blob_thread, &args[2]); + get_one_blob_thread (&args[0]); + xpthread_join (thr1); + xpthread_join (thr2); +} + +/* Continuously fork subprocesses to obtain a little bit of + randomness. */ +static void * +fork_thread (void *closure) +{ + void (*func)(unsigned char *, size_t) = closure; + + struct dynarray_blob result; + dynarray_blob_init (&result); + + /* Three blobs from each subprocess. */ + struct blob *shared_result + = support_shared_allocate (3 * sizeof (*shared_result)); + + while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED)) + { + /* Obtain the results from a subprocess. */ + struct subprocess_args arg = { shared_result, func }; + support_isolate_in_subprocess (fork_thread_subprocess, &arg); + + for (int i = 0; i < 3; ++i) + { + struct blob *place = dynarray_blob_emplace (&result); + TEST_VERIFY_EXIT (place != NULL); + place->size = shared_result[i].size; + place->thread_id = -1; + place->index = i; + memcpy (place->bytes, shared_result[i].bytes, place->size); + } + } + + support_shared_free (shared_result); + + copy_result_to_global (&result); + dynarray_blob_free (&result); + + return NULL; +} + +/* Launch the outer threads and wait for their termination. */ +static void +run_outer_threads (void (*func)(unsigned char *, size_t)) +{ + /* Special thread that continuously calls fork. */ + pthread_t fork_thread_id = xpthread_create (NULL, fork_thread, func); + + pthread_t threads[outer_threads]; + for (int i = 0; i < outer_threads; ++i) + threads[i] = xpthread_create (NULL, outer_thread, func); + + for (int i = 0; i < outer_threads; ++i) + xpthread_join (threads[i]); + + __atomic_store_n (&termination_requested, true, __ATOMIC_RELAXED); + xpthread_join (fork_thread_id); +} + +static int +do_test_func (const char *fname, void (*func)(unsigned char *, size_t)) +{ + dynarray_blob_init (&global_result); + int expected_blobs + = count_per_thread * inner_threads * outer_threads * outer_rounds; + printf ("info: %s: minimum of %d blob results expected\n", + fname, expected_blobs); + + run_outer_threads (func); + + /* The forking thread delivers a non-deterministic number of + results, which is why expected_blobs is only a minimun number of + results. */ + printf ("info: %s: %zu blob results observed\n", fname, + dynarray_blob_size (&global_result)); + TEST_VERIFY (dynarray_blob_size (&global_result) >= expected_blobs); + + /* Verify that there are no duplicates. */ + qsort (dynarray_blob_begin (&global_result), + dynarray_blob_size (&global_result), + sizeof (struct blob), compare_blob); + struct blob *end = dynarray_blob_end (&global_result); + for (struct blob *p = dynarray_blob_begin (&global_result) + 1; + p < end; ++p) + { + if (compare_blob (p - 1, p) == 0) + { + support_record_failure (); + char *quoted = support_quote_blob (p->bytes, p->size); + printf ("error: %s: duplicate blob: \"%s\" (%d bytes)\n", + fname, quoted, (int) p->size); + printf (" first source: thread %d, index %u\n", + p[-1].thread_id, p[-1].index); + printf (" second source: thread %d, index %u\n", + p[0].thread_id, p[0].index); + free (quoted); + } + } + + dynarray_blob_free (&global_result); + + return 0; +} + +static int +do_test (void) +{ + do_test_func ("arc4random", generate_arc4random); + do_test_func ("arc4random_buf", generate_arc4random_buf); + do_test_func ("arc4random_uniform", generate_arc4random_uniform); + + return 0; +} + +#include <support/test-driver.c> |