Add multi-threaded unit test

The unit test we add is designed to fail. The goal is to test the tests
and show that they catch the problem. A later commit will fix the unit
test and will make it pass.

Signed-off-by: Janos Follath <janos.follath@arm.com>
diff --git a/tests/suites/test_suite_ctr_drbg.data b/tests/suites/test_suite_ctr_drbg.data
index a72d8af..70206e7 100644
--- a/tests/suites/test_suite_ctr_drbg.data
+++ b/tests/suites/test_suite_ctr_drbg.data
@@ -1096,5 +1096,11 @@
 CTR_DRBG Special Behaviours
 ctr_drbg_special_behaviours:
 
+CTR_DRBG Threads: no reseed
+ctr_drbg_threads:"1fafa98bc83d95e10f2d5ed339a553e1":10000
+
+CTR_DRBG Threads: reseed
+ctr_drbg_threads:"0d2dda60286dc738ddcc2dd3520bb988":25
+
 CTR_DRBG self test
 ctr_drbg_selftest:
diff --git a/tests/suites/test_suite_ctr_drbg.function b/tests/suites/test_suite_ctr_drbg.function
index 066e70b..bdf3dca 100644
--- a/tests/suites/test_suite_ctr_drbg.function
+++ b/tests/suites/test_suite_ctr_drbg.function
@@ -90,6 +90,19 @@
     mbedtls_ctr_drbg_free(&ctx);
 }
 
+static const int thread_random_reps = 10;
+void *thread_random_function( void* ctx )
+{
+    unsigned char out[16];
+    memset(out, 0, sizeof(out));
+
+    for(int i = 0; i < thread_random_reps; i++) {
+        TEST_EQUAL(mbedtls_ctr_drbg_random_with_add((mbedtls_ctr_drbg_context*) ctx, out, sizeof(out), NULL, 0), 0);
+    }
+
+exit:
+    return NULL;
+}
 /* END_HEADER */
 
 /* BEGIN_DEPENDENCIES
@@ -325,6 +338,57 @@
 }
 /* END_CASE */
 
+/* BEGIN_CASE depends_on:MBEDTLS_THREADING_PTHREAD */
+void ctr_drbg_threads(data_t *expected_result, int reseed_interval)
+{
+#define THREAD_CNT 5
+    pthread_t threads[THREAD_CNT];
+
+    unsigned char out[16];
+    memset(out, 0, sizeof(out));
+
+    unsigned char entropy[1024];
+    memset(entropy, 0, sizeof(entropy));
+
+    test_offset_idx = 0;
+    test_max_idx = sizeof(entropy);
+
+    mbedtls_ctr_drbg_context ctx;
+    mbedtls_ctr_drbg_init(&ctx);
+
+    mbedtls_ctr_drbg_set_reseed_interval(&ctx, reseed_interval);
+
+    /* There are too many calls in this test to conveniently provide enough
+     * entropy for this to be on. Test cases can trigger reseeding by setting
+     * \p reseed_interval appropriately. */
+    mbedtls_ctr_drbg_set_prediction_resistance(&ctx, MBEDTLS_CTR_DRBG_PR_OFF);
+
+    TEST_EQUAL(
+        mbedtls_ctr_drbg_seed(&ctx, mbedtls_test_entropy_func, entropy, NULL, 0),
+        0);
+
+    for (size_t i = 0; i < THREAD_CNT; i++) {
+        TEST_EQUAL(
+            pthread_create(&threads[i], NULL,
+                           thread_random_function, (void*) &ctx),
+            0);
+    }
+
+    for (size_t i = 0; i < THREAD_CNT; i++) {
+        TEST_EQUAL(pthread_join(threads[i], NULL), 0);
+    }
+
+    /* Take a last output for comparing and thus verifying the DRBG state */
+    TEST_EQUAL(mbedtls_ctr_drbg_random(&ctx, out, sizeof(out)), 0);
+
+    TEST_MEMORY_COMPARE(out, sizeof(out), expected_result->x, expected_result->len);
+
+exit:
+    mbedtls_ctr_drbg_free(&ctx);
+}
+#undef THREAD_CNT
+/* END_CASE */
+
 /* BEGIN_CASE depends_on:MBEDTLS_FS_IO */
 void ctr_drbg_seed_file(char *path, int ret)
 {