X-Git-Url: https://git.openssl.org/gitweb/?a=blobdiff_plain;f=test%2Fthreadstest.c;h=13405f4948cdeb59552b7992a3ef98cf0fbf5e86;hb=HEAD;hp=26807e294c392d413348ffb74f62a42c3de2cb1f;hpb=2f17e978a0ec5becda8a61dcf3e7840740ccdfd3;p=openssl.git diff --git a/test/threadstest.c b/test/threadstest.c index 26807e294c..2d05255132 100644 --- a/test/threadstest.c +++ b/test/threadstest.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 2016-2024 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -7,99 +7,494 @@ * https://www.openssl.org/source/license.html */ +/* + * The test_multi_downgrade_shared_pkey function tests the thread safety of a + * deprecated function. + */ +#ifndef OPENSSL_NO_DEPRECATED_3_0 +# define OPENSSL_SUPPRESS_DEPRECATED +#endif + #if defined(_WIN32) # include #endif #include #include -#include -#include #include +#include +#include +#include +#include +#include +#include "internal/tsan_assist.h" +#include "internal/nelem.h" +#include "internal/time.h" +#include "internal/rcu.h" #include "testutil.h" +#include "threadstest.h" + +#ifdef __SANITIZE_THREAD__ +#include +#define TSAN_ACQUIRE(s) __tsan_acquire(s) +#else +#define TSAN_ACQUIRE(s) +#endif + +/* Limit the maximum number of threads */ +#define MAXIMUM_THREADS 10 + +/* Limit the maximum number of providers loaded into a library context */ +#define MAXIMUM_PROVIDERS 4 static int do_fips = 0; static char *privkey; +static char *config_file = NULL; +static int multidefault_run = 0; + +static const char *default_provider[] = { "default", NULL }; +static const char *fips_provider[] = { "fips", NULL }; +static const char *fips_and_default_providers[] = { "default", "fips", NULL }; -#if !defined(OPENSSL_THREADS) || defined(CRYPTO_TDEBUG) +static CRYPTO_RWLOCK *global_lock; -typedef unsigned int thread_t; +#ifdef TSAN_REQUIRES_LOCKING +static CRYPTO_RWLOCK *tsan_lock; +#endif -static int run_thread(thread_t *t, void (*f)(void)) +/* Grab a globally unique integer value, return 0 on failure */ +static int get_new_uid(void) { - f(); - return 1; + /* + * Start with a nice large number to avoid potential conflicts when + * we generate a new OID. + */ + static TSAN_QUALIFIER int current_uid = 1 << (sizeof(int) * 8 - 2); +#ifdef TSAN_REQUIRES_LOCKING + int r; + + if (!TEST_true(CRYPTO_THREAD_write_lock(tsan_lock))) + return 0; + r = ++current_uid; + if (!TEST_true(CRYPTO_THREAD_unlock(tsan_lock))) + return 0; + return r; + +#else + return tsan_counter(¤t_uid); +#endif } -static int wait_for_thread(thread_t thread) +static int test_lock(void) { - return 1; + CRYPTO_RWLOCK *lock = CRYPTO_THREAD_lock_new(); + int res; + + if (!TEST_ptr(lock)) + return 0; + + res = TEST_true(CRYPTO_THREAD_read_lock(lock)) + && TEST_true(CRYPTO_THREAD_unlock(lock)) + && TEST_true(CRYPTO_THREAD_write_lock(lock)) + && TEST_true(CRYPTO_THREAD_unlock(lock)); + + CRYPTO_THREAD_lock_free(lock); + + return res; } -#elif defined(OPENSSL_SYS_WINDOWS) +#if defined(OPENSSL_THREADS) +static int contention = 0; +static int rwwriter1_done = 0; +static int rwwriter2_done = 0; +static int rwreader1_iterations = 0; +static int rwreader2_iterations = 0; +static int rwwriter1_iterations = 0; +static int rwwriter2_iterations = 0; +static int *rwwriter_ptr = NULL; +static int rw_torture_result = 1; +static CRYPTO_RWLOCK *rwtorturelock = NULL; +static CRYPTO_RWLOCK *atomiclock = NULL; + +static void rwwriter_fn(int id, int *iterations) +{ + int count; + int *old, *new; + OSSL_TIME t1, t2; + t1 = ossl_time_now(); + + for (count = 0; ; count++) { + new = CRYPTO_zalloc(sizeof (int), NULL, 0); + if (contention == 0) + OSSL_sleep(1000); + if (!CRYPTO_THREAD_write_lock(rwtorturelock)) + abort(); + if (rwwriter_ptr != NULL) { + *new = *rwwriter_ptr + 1; + } else { + *new = 0; + } + old = rwwriter_ptr; + rwwriter_ptr = new; + if (!CRYPTO_THREAD_unlock(rwtorturelock)) + abort(); + if (old != NULL) + CRYPTO_free(old, __FILE__, __LINE__); + t2 = ossl_time_now(); + if ((ossl_time2seconds(t2) - ossl_time2seconds(t1)) >= 4) + break; + } + *iterations = count; + return; +} + +static void rwwriter1_fn(void) +{ + int local; + + TEST_info("Starting writer1"); + rwwriter_fn(1, &rwwriter1_iterations); + CRYPTO_atomic_add(&rwwriter1_done, 1, &local, atomiclock); +} + +static void rwwriter2_fn(void) +{ + int local; -typedef HANDLE thread_t; + TEST_info("Starting writer 2"); + rwwriter_fn(2, &rwwriter2_iterations); + CRYPTO_atomic_add(&rwwriter2_done, 1, &local, atomiclock); +} -static DWORD WINAPI thread_run(LPVOID arg) +static void rwreader_fn(int *iterations) { - void (*f)(void); + unsigned int count = 0; - *(void **) (&f) = arg; + int old = 0; + int lw1 = 0; + int lw2 = 0; - f(); - return 0; + if (CRYPTO_THREAD_read_lock(rwtorturelock) == 0) + abort(); + + while (lw1 != 1 || lw2 != 1) { + CRYPTO_atomic_add(&rwwriter1_done, 0, &lw1, atomiclock); + CRYPTO_atomic_add(&rwwriter2_done, 0, &lw2, atomiclock); + + count++; + if (rwwriter_ptr != NULL && old > *rwwriter_ptr) { + TEST_info("rwwriter pointer went backwards\n"); + rw_torture_result = 0; + } + if (CRYPTO_THREAD_unlock(rwtorturelock) == 0) + abort(); + *iterations = count; + if (rw_torture_result == 0) { + *iterations = count; + return; + } + if (CRYPTO_THREAD_read_lock(rwtorturelock) == 0) + abort(); + } + *iterations = count; + if (CRYPTO_THREAD_unlock(rwtorturelock) == 0) + abort(); } -static int run_thread(thread_t *t, void (*f)(void)) +static void rwreader1_fn(void) { - *t = CreateThread(NULL, 0, thread_run, *(void **) &f, 0, NULL); - return *t != NULL; + TEST_info("Starting reader 1"); + rwreader_fn(&rwreader1_iterations); } -static int wait_for_thread(thread_t thread) +static void rwreader2_fn(void) { - return WaitForSingleObject(thread, INFINITE) == 0; + TEST_info("Starting reader 2"); + rwreader_fn(&rwreader2_iterations); } -#else +static thread_t rwwriter1; +static thread_t rwwriter2; +static thread_t rwreader1; +static thread_t rwreader2; + +static int _torture_rw(void) +{ + double tottime = 0; + int ret = 0; + double avr, avw; + OSSL_TIME t1, t2; + struct timeval dtime; + + rwtorturelock = CRYPTO_THREAD_lock_new(); + atomiclock = CRYPTO_THREAD_lock_new(); + if (!TEST_ptr(rwtorturelock) || !TEST_ptr(atomiclock)) + goto out; + + rwwriter1_iterations = 0; + rwwriter2_iterations = 0; + rwreader1_iterations = 0; + rwreader2_iterations = 0; + rwwriter1_done = 0; + rwwriter2_done = 0; + rw_torture_result = 1; + + memset(&rwwriter1, 0, sizeof(thread_t)); + memset(&rwwriter2, 0, sizeof(thread_t)); + memset(&rwreader1, 0, sizeof(thread_t)); + memset(&rwreader2, 0, sizeof(thread_t)); + + TEST_info("Staring rw torture"); + t1 = ossl_time_now(); + if (!TEST_true(run_thread(&rwreader1, rwreader1_fn)) + || !TEST_true(run_thread(&rwreader2, rwreader2_fn)) + || !TEST_true(run_thread(&rwwriter1, rwwriter1_fn)) + || !TEST_true(run_thread(&rwwriter2, rwwriter2_fn)) + || !TEST_true(wait_for_thread(rwwriter1)) + || !TEST_true(wait_for_thread(rwwriter2)) + || !TEST_true(wait_for_thread(rwreader1)) + || !TEST_true(wait_for_thread(rwreader2))) + goto out; + + t2 = ossl_time_now(); + dtime = ossl_time_to_timeval(ossl_time_subtract(t2, t1)); + tottime = dtime.tv_sec + (dtime.tv_usec / 1e6); + TEST_info("rw_torture_result is %d\n", rw_torture_result); + TEST_info("performed %d reads and %d writes over 2 read and 2 write threads in %e seconds", + rwreader1_iterations + rwreader2_iterations, + rwwriter1_iterations + rwwriter2_iterations, tottime); + if ((rwreader1_iterations + rwreader2_iterations == 0) + || (rwwriter1_iterations + rwwriter2_iterations == 0)) { + TEST_info("Threads did not iterate\n"); + goto out; + } + avr = tottime / (rwreader1_iterations + rwreader2_iterations); + avw = (tottime / (rwwriter1_iterations + rwwriter2_iterations)); + TEST_info("Average read time %e/read", avr); + TEST_info("Averate write time %e/write", avw); + + if (TEST_int_eq(rw_torture_result, 1)) + ret = 1; +out: + CRYPTO_THREAD_lock_free(rwtorturelock); + CRYPTO_THREAD_lock_free(atomiclock); + rwtorturelock = NULL; + return ret; +} -typedef pthread_t thread_t; +static int torture_rw_low(void) +{ + contention = 0; + return _torture_rw(); +} + +static int torture_rw_high(void) +{ + contention = 1; + return _torture_rw(); +} + + +# ifndef OPENSSL_SYS_MACOSX +static CRYPTO_RCU_LOCK *rcu_lock = NULL; + +static int writer1_done = 0; +static int writer2_done = 0; +static int reader1_iterations = 0; +static int reader2_iterations = 0; +static int writer1_iterations = 0; +static int writer2_iterations = 0; +static uint64_t *writer_ptr = NULL; +static uint64_t global_ctr = 0; +static int rcu_torture_result = 1; +static void free_old_rcu_data(void *data) +{ + CRYPTO_free(data, NULL, 0); +} -static void *thread_run(void *arg) +static void writer_fn(int id, int *iterations) { - void (*f)(void); + int count; + OSSL_TIME t1, t2; + uint64_t *old, *new; + + t1 = ossl_time_now(); + + for (count = 0; ; count++) { + new = CRYPTO_zalloc(sizeof(uint64_t), NULL, 0); + if (contention == 0) + OSSL_sleep(1000); + ossl_rcu_write_lock(rcu_lock); + old = ossl_rcu_deref(&writer_ptr); + TSAN_ACQUIRE(&writer_ptr); + *new = global_ctr++; + ossl_rcu_assign_ptr(&writer_ptr, &new); + if (contention == 0) + ossl_rcu_call(rcu_lock, free_old_rcu_data, old); + ossl_rcu_write_unlock(rcu_lock); + if (contention != 0) { + ossl_synchronize_rcu(rcu_lock); + CRYPTO_free(old, NULL, 0); + } + t2 = ossl_time_now(); + if ((ossl_time2seconds(t2) - ossl_time2seconds(t1)) >= 4) + break; + } + *iterations = count; + return; +} - *(void **) (&f) = arg; +static void writer1_fn(void) +{ + int local; - f(); - return NULL; + TEST_info("Starting writer1"); + writer_fn(1, &writer1_iterations); + CRYPTO_atomic_add(&writer1_done, 1, &local, atomiclock); } -static int run_thread(thread_t *t, void (*f)(void)) +static void writer2_fn(void) { - return pthread_create(t, NULL, thread_run, *(void **) &f) == 0; + int local; + + TEST_info("Starting writer2"); + writer_fn(2, &writer2_iterations); + CRYPTO_atomic_add(&writer2_done, 1, &local, atomiclock); } -static int wait_for_thread(thread_t thread) +static void reader_fn(int *iterations) { - return pthread_join(thread, NULL) == 0; + unsigned int count = 0; + uint64_t *valp; + uint64_t val; + uint64_t oldval = 0; + int lw1 = 0; + int lw2 = 0; + + while (lw1 != 1 || lw2 != 1) { + CRYPTO_atomic_add(&writer1_done, 0, &lw1, atomiclock); + CRYPTO_atomic_add(&writer2_done, 0, &lw2, atomiclock); + count++; + ossl_rcu_read_lock(rcu_lock); + valp = ossl_rcu_deref(&writer_ptr); + val = (valp == NULL) ? 0 : *valp; + + if (oldval > val) { + TEST_info("rcu torture value went backwards! %llu : %llu", (unsigned long long)oldval, (unsigned long long)val); + rcu_torture_result = 0; + } + oldval = val; /* just try to deref the pointer */ + ossl_rcu_read_unlock(rcu_lock); + if (rcu_torture_result == 0) { + *iterations = count; + return; + } + } + *iterations = count; } -#endif +static void reader1_fn(void) +{ + TEST_info("Starting reader 1"); + reader_fn(&reader1_iterations); +} -static int test_lock(void) +static void reader2_fn(void) { - CRYPTO_RWLOCK *lock = CRYPTO_THREAD_lock_new(); + TEST_info("Starting reader 2"); + reader_fn(&reader2_iterations); +} - if (!TEST_true(CRYPTO_THREAD_read_lock(lock)) - || !TEST_true(CRYPTO_THREAD_unlock(lock))) +static thread_t writer1; +static thread_t writer2; +static thread_t reader1; +static thread_t reader2; + +static int _torture_rcu(void) +{ + OSSL_TIME t1, t2; + struct timeval dtime; + double tottime; + double avr, avw; + int rc = 0; + + atomiclock = CRYPTO_THREAD_lock_new(); + if (!TEST_ptr(atomiclock)) + goto out; + + memset(&writer1, 0, sizeof(thread_t)); + memset(&writer2, 0, sizeof(thread_t)); + memset(&reader1, 0, sizeof(thread_t)); + memset(&reader2, 0, sizeof(thread_t)); + + writer1_iterations = 0; + writer2_iterations = 0; + reader1_iterations = 0; + reader2_iterations = 0; + writer1_done = 0; + writer2_done = 0; + rcu_torture_result = 1; + + rcu_lock = ossl_rcu_lock_new(1, NULL); + if (!rcu_lock) + goto out; + + TEST_info("Staring rcu torture"); + t1 = ossl_time_now(); + if (!TEST_true(run_thread(&reader1, reader1_fn)) + || !TEST_true(run_thread(&reader2, reader2_fn)) + || !TEST_true(run_thread(&writer1, writer1_fn)) + || !TEST_true(run_thread(&writer2, writer2_fn)) + || !TEST_true(wait_for_thread(writer1)) + || !TEST_true(wait_for_thread(writer2)) + || !TEST_true(wait_for_thread(reader1)) + || !TEST_true(wait_for_thread(reader2))) + goto out; + + t2 = ossl_time_now(); + dtime = ossl_time_to_timeval(ossl_time_subtract(t2, t1)); + tottime = dtime.tv_sec + (dtime.tv_usec / 1e6); + TEST_info("rcu_torture_result is %d\n", rcu_torture_result); + TEST_info("performed %d reads and %d writes over 2 read and 2 write threads in %e seconds", + reader1_iterations + reader2_iterations, + writer1_iterations + writer2_iterations, tottime); + if ((reader1_iterations + reader2_iterations == 0) + || (writer1_iterations + writer2_iterations == 0)) { + TEST_info("Threads did not iterate\n"); + goto out; + } + avr = tottime / (reader1_iterations + reader2_iterations); + avw = tottime / (writer1_iterations + writer2_iterations); + TEST_info("Average read time %e/read", avr); + TEST_info("Average write time %e/write", avw); + + if (!TEST_int_eq(rcu_torture_result, 1)) + goto out; + + rc = 1; +out: + ossl_rcu_lock_free(rcu_lock); + CRYPTO_THREAD_lock_free(atomiclock); + if (!TEST_int_eq(rcu_torture_result, 1)) return 0; - CRYPTO_THREAD_lock_free(lock); + return rc; +} - return 1; +static int torture_rcu_low(void) +{ + contention = 0; + return _torture_rcu(); } +static int torture_rcu_high(void) +{ + contention = 1; + return _torture_rcu(); +} +# endif +#endif + static CRYPTO_ONCE once_run = CRYPTO_ONCE_STATIC_INIT; static unsigned once_run_count = 0; @@ -267,6 +662,107 @@ static int test_atomic(void) static OSSL_LIB_CTX *multi_libctx = NULL; static int multi_success; +static OSSL_PROVIDER *multi_provider[MAXIMUM_PROVIDERS + 1]; +static size_t multi_num_threads; +static thread_t multi_threads[MAXIMUM_THREADS]; + +static void multi_intialise(void) +{ + multi_success = 1; + multi_libctx = NULL; + multi_num_threads = 0; + memset(multi_threads, 0, sizeof(multi_threads)); + memset(multi_provider, 0, sizeof(multi_provider)); +} + +static void multi_set_success(int ok) +{ + if (CRYPTO_THREAD_write_lock(global_lock) == 0) { + /* not synchronized, but better than not reporting failure */ + multi_success = ok; + return; + } + + multi_success = ok; + + CRYPTO_THREAD_unlock(global_lock); +} + +static void thead_teardown_libctx(void) +{ + OSSL_PROVIDER **p; + + for (p = multi_provider; *p != NULL; p++) + OSSL_PROVIDER_unload(*p); + OSSL_LIB_CTX_free(multi_libctx); + multi_intialise(); +} + +static int thread_setup_libctx(int libctx, const char *providers[]) +{ + size_t n; + + if (libctx && !TEST_true(test_get_libctx(&multi_libctx, NULL, config_file, + NULL, NULL))) + return 0; + + if (providers != NULL) + for (n = 0; providers[n] != NULL; n++) + if (!TEST_size_t_lt(n, MAXIMUM_PROVIDERS) + || !TEST_ptr(multi_provider[n] = OSSL_PROVIDER_load(multi_libctx, + providers[n]))) { + thead_teardown_libctx(); + return 0; + } + return 1; +} + +static int teardown_threads(void) +{ + size_t i; + + for (i = 0; i < multi_num_threads; i++) + if (!TEST_true(wait_for_thread(multi_threads[i]))) + return 0; + return 1; +} + +static int start_threads(size_t n, void (*thread_func)(void)) +{ + size_t i; + + if (!TEST_size_t_le(multi_num_threads + n, MAXIMUM_THREADS)) + return 0; + + for (i = 0 ; i < n; i++) + if (!TEST_true(run_thread(multi_threads + multi_num_threads++, thread_func))) + return 0; + return 1; +} + +/* Template multi-threaded test function */ +static int thread_run_test(void (*main_func)(void), + size_t num_threads, void (*thread_func)(void), + int libctx, const char *providers[]) +{ + int testresult = 0; + + multi_intialise(); + if (!thread_setup_libctx(libctx, providers) + || !start_threads(num_threads, thread_func)) + goto err; + + if (main_func != NULL) + main_func(); + + if (!teardown_threads() + || !TEST_true(multi_success)) + goto err; + testresult = 1; + err: + thead_teardown_libctx(); + return testresult; +} static void thread_general_worker(void) { @@ -288,7 +784,6 @@ static void thread_general_worker(void) }; unsigned int mdoutl; int ciphoutl; - EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL; int testresult = 0; int i, isfips; @@ -317,18 +812,13 @@ static void thread_general_worker(void) goto err; } - pctx = EVP_PKEY_CTX_new_from_name(multi_libctx, "RSA", NULL); - if (!TEST_ptr(pctx) - || !TEST_int_gt(EVP_PKEY_keygen_init(pctx), 0) - /* - * We want the test to run quickly - not securely. Therefore we - * use an insecure bit length where we can (512). In the FIPS - * module though we must use a longer length. - */ - || !TEST_int_gt(EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, - isfips ? 2048 : 512), - 0) - || !TEST_int_gt(EVP_PKEY_keygen(pctx, &pkey), 0)) + /* + * We want the test to run quickly - not securely. + * Therefore we use an insecure bit length where we can (512). + * In the FIPS module though we must use a longer length. + */ + pkey = EVP_PKEY_Q_keygen(multi_libctx, NULL, "RSA", isfips ? 2048 : 512); + if (!TEST_ptr(pkey)) goto err; testresult = 1; @@ -337,20 +827,19 @@ static void thread_general_worker(void) EVP_MD_free(md); EVP_CIPHER_CTX_free(cipherctx); EVP_CIPHER_free(ciph); - EVP_PKEY_CTX_free(pctx); EVP_PKEY_free(pkey); if (!testresult) - multi_success = 0; + multi_set_success(0); } static void thread_multi_simple_fetch(void) { - EVP_MD *md = EVP_MD_fetch(NULL, "SHA2-256", NULL); + EVP_MD *md = EVP_MD_fetch(multi_libctx, "SHA2-256", NULL); if (md != NULL) EVP_MD_free(md); else - multi_success = 0; + multi_set_success(0); } static EVP_PKEY *shared_evp_pkey = NULL; @@ -360,7 +849,7 @@ static void thread_shared_evp_pkey(void) char *msg = "Hello World"; unsigned char ctbuf[256]; unsigned char ptbuf[256]; - size_t ptlen = sizeof(ptbuf), ctlen = sizeof(ctbuf); + size_t ptlen, ctlen = sizeof(ctbuf); EVP_PKEY_CTX *ctx = NULL; int success = 0; int i; @@ -386,8 +875,9 @@ static void thread_shared_evp_pkey(void) if (!TEST_ptr(ctx)) goto err; + ptlen = sizeof(ptbuf); if (!TEST_int_ge(EVP_PKEY_decrypt_init(ctx), 0) - || !TEST_int_ge(EVP_PKEY_decrypt(ctx, ptbuf, &ptlen, ctbuf, ctlen), + || !TEST_int_gt(EVP_PKEY_decrypt(ctx, ptbuf, &ptlen, ctbuf, ctlen), 0) || !TEST_mem_eq(msg, strlen(msg), ptbuf, ptlen)) goto err; @@ -398,113 +888,305 @@ static void thread_shared_evp_pkey(void) err: EVP_PKEY_CTX_free(ctx); if (!success) - multi_success = 0; + multi_set_success(0); } -/* - * Do work in multiple worker threads at the same time. - * Test 0: General worker, using the default provider - * Test 1: General worker, using the fips provider - * Test 2: Simple fetch worker - * Test 3: Worker using a shared EVP_PKEY - */ -static int test_multi(int idx) +static void thread_provider_load_unload(void) { - thread_t thread1, thread2; - int testresult = 0; - OSSL_PROVIDER *prov = NULL, *prov2 = NULL; - void (*worker)(void); + OSSL_PROVIDER *deflt = OSSL_PROVIDER_load(multi_libctx, "default"); - if (idx == 1 && !do_fips) + if (!TEST_ptr(deflt) + || !TEST_true(OSSL_PROVIDER_available(multi_libctx, "default"))) + multi_set_success(0); + + OSSL_PROVIDER_unload(deflt); +} + +static int test_multi_general_worker_default_provider(void) +{ + return thread_run_test(&thread_general_worker, 2, &thread_general_worker, + 1, default_provider); +} + +static int test_multi_general_worker_fips_provider(void) +{ + if (!do_fips) return TEST_skip("FIPS not supported"); + return thread_run_test(&thread_general_worker, 2, &thread_general_worker, + 1, fips_provider); +} - multi_success = 1; - multi_libctx = OSSL_LIB_CTX_new(); - if (!TEST_ptr(multi_libctx)) +static int test_multi_fetch_worker(void) +{ + return thread_run_test(&thread_multi_simple_fetch, + 2, &thread_multi_simple_fetch, 1, default_provider); +} + +static int test_multi_shared_pkey_common(void (*worker)(void)) +{ + int testresult = 0; + + multi_intialise(); + if (!thread_setup_libctx(1, do_fips ? fips_and_default_providers + : default_provider) + || !TEST_ptr(shared_evp_pkey = load_pkey_pem(privkey, multi_libctx)) + || !start_threads(1, &thread_shared_evp_pkey) + || !start_threads(1, worker)) goto err; - prov = OSSL_PROVIDER_load(multi_libctx, (idx == 1) ? "fips" : "default"); - if (!TEST_ptr(prov)) + + thread_shared_evp_pkey(); + + if (!teardown_threads() + || !TEST_true(multi_success)) goto err; + testresult = 1; + err: + EVP_PKEY_free(shared_evp_pkey); + thead_teardown_libctx(); + return testresult; +} - switch (idx) { - case 0: - case 1: - worker = thread_general_worker; - break; - case 2: - worker = thread_multi_simple_fetch; - break; - case 3: - /* - * If available we have both the default and fips providers for this - * test - */ - if (do_fips - && !TEST_ptr(prov2 = OSSL_PROVIDER_load(multi_libctx, "fips"))) - goto err; - if (!TEST_ptr(shared_evp_pkey = load_pkey_pem(privkey, multi_libctx))) - goto err; - worker = thread_shared_evp_pkey; - break; - default: - TEST_error("Invalid test index"); +#ifndef OPENSSL_NO_DEPRECATED_3_0 +static void thread_downgrade_shared_evp_pkey(void) +{ + /* + * This test is only relevant for deprecated functions that perform + * downgrading + */ + if (EVP_PKEY_get0_RSA(shared_evp_pkey) == NULL) + multi_set_success(0); +} + +static int test_multi_downgrade_shared_pkey(void) +{ + return test_multi_shared_pkey_common(&thread_downgrade_shared_evp_pkey); +} +#endif + +static int test_multi_shared_pkey(void) +{ + return test_multi_shared_pkey_common(&thread_shared_evp_pkey); +} + +static int test_multi_load_unload_provider(void) +{ + EVP_MD *sha256 = NULL; + OSSL_PROVIDER *prov = NULL; + int testresult = 0; + + multi_intialise(); + if (!thread_setup_libctx(1, NULL) + || !TEST_ptr(prov = OSSL_PROVIDER_load(multi_libctx, "default")) + || !TEST_ptr(sha256 = EVP_MD_fetch(multi_libctx, "SHA2-256", NULL)) + || !TEST_true(OSSL_PROVIDER_unload(prov))) goto err; - } + prov = NULL; - if (!TEST_true(run_thread(&thread1, worker)) - || !TEST_true(run_thread(&thread2, worker))) + if (!start_threads(2, &thread_provider_load_unload)) goto err; - worker(); + thread_provider_load_unload(); - if (!TEST_true(wait_for_thread(thread1)) - || !TEST_true(wait_for_thread(thread2)) + if (!teardown_threads() || !TEST_true(multi_success)) goto err; - testresult = 1; - err: OSSL_PROVIDER_unload(prov); - OSSL_PROVIDER_unload(prov2); - OSSL_LIB_CTX_free(multi_libctx); - EVP_PKEY_free(shared_evp_pkey); - shared_evp_pkey = NULL; + EVP_MD_free(sha256); + thead_teardown_libctx(); return testresult; } +static char *multi_load_provider = "legacy"; /* * This test attempts to load several providers at the same time, and if * run with a thread sanitizer, should crash if the core provider code * doesn't synchronize well enough. */ -#define MULTI_LOAD_THREADS 3 static void test_multi_load_worker(void) { OSSL_PROVIDER *prov; - TEST_ptr(prov = OSSL_PROVIDER_load(NULL, "default")); - TEST_true(OSSL_PROVIDER_unload(prov)); + if (!TEST_ptr(prov = OSSL_PROVIDER_load(multi_libctx, multi_load_provider)) + || !TEST_true(OSSL_PROVIDER_unload(prov))) + multi_set_success(0); +} + +static int test_multi_default(void) +{ + /* Avoid running this test twice */ + if (multidefault_run) { + TEST_skip("multi default test already run"); + return 1; + } + multidefault_run = 1; + + return thread_run_test(&thread_multi_simple_fetch, + 2, &thread_multi_simple_fetch, 0, default_provider); } static int test_multi_load(void) { - thread_t threads[MULTI_LOAD_THREADS]; - int i; + int res = 1; + OSSL_PROVIDER *prov; - for (i = 0; i < MULTI_LOAD_THREADS; i++) - TEST_true(run_thread(&threads[i], test_multi_load_worker)); + /* The multidefault test must run prior to this test */ + if (!multidefault_run) { + TEST_info("Running multi default test first"); + res = test_multi_default(); + } - for (i = 0; i < MULTI_LOAD_THREADS; i++) - TEST_true(wait_for_thread(threads[i])); + /* + * We use the legacy provider in test_multi_load_worker because it uses a + * child libctx that might hit more codepaths that might be sensitive to + * threading issues. But in a no-legacy build that won't be loadable so + * we use the default provider instead. + */ + prov = OSSL_PROVIDER_load(NULL, "legacy"); + if (prov == NULL) { + TEST_info("Cannot load legacy provider - assuming this is a no-legacy build"); + multi_load_provider = "default"; + } + OSSL_PROVIDER_unload(prov); - return 1; + return thread_run_test(NULL, MAXIMUM_THREADS, &test_multi_load_worker, 0, + NULL) && res; +} + +static void test_obj_create_one(void) +{ + char tids[12], oid[40], sn[30], ln[30]; + int id = get_new_uid(); + + BIO_snprintf(tids, sizeof(tids), "%d", id); + BIO_snprintf(oid, sizeof(oid), "1.3.6.1.4.1.16604.%s", tids); + BIO_snprintf(sn, sizeof(sn), "short-name-%s", tids); + BIO_snprintf(ln, sizeof(ln), "long-name-%s", tids); + if (!TEST_int_ne(id, 0) + || !TEST_true(id = OBJ_create(oid, sn, ln)) + || !TEST_true(OBJ_add_sigid(id, NID_sha3_256, NID_rsa))) + multi_set_success(0); +} + +static int test_obj_add(void) +{ + return thread_run_test(&test_obj_create_one, + MAXIMUM_THREADS, &test_obj_create_one, + 1, default_provider); +} + +#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK) +static BIO *multi_bio1, *multi_bio2; + +static void test_bio_dgram_pair_worker(void) +{ + ossl_unused int r; + int ok = 0; + uint8_t ch = 0; + uint8_t scratch[64]; + BIO_MSG msg = {0}; + size_t num_processed = 0; + + if (!TEST_int_eq(RAND_bytes_ex(multi_libctx, &ch, 1, 64), 1)) + goto err; + + msg.data = scratch; + msg.data_len = sizeof(scratch); + + /* + * We do not test for failure here as recvmmsg may fail if no sendmmsg + * has been called yet. The purpose of this code is to exercise tsan. + */ + if (ch & 2) + r = BIO_sendmmsg(ch & 1 ? multi_bio2 : multi_bio1, &msg, + sizeof(BIO_MSG), 1, 0, &num_processed); + else + r = BIO_recvmmsg(ch & 1 ? multi_bio2 : multi_bio1, &msg, + sizeof(BIO_MSG), 1, 0, &num_processed); + + ok = 1; +err: + if (ok == 0) + multi_set_success(0); +} + +static int test_bio_dgram_pair(void) +{ + int r; + BIO *bio1 = NULL, *bio2 = NULL; + + r = BIO_new_bio_dgram_pair(&bio1, 0, &bio2, 0); + if (!TEST_int_eq(r, 1)) + goto err; + + multi_bio1 = bio1; + multi_bio2 = bio2; + + r = thread_run_test(&test_bio_dgram_pair_worker, + MAXIMUM_THREADS, &test_bio_dgram_pair_worker, + 1, default_provider); + +err: + BIO_free(bio1); + BIO_free(bio2); + return r; +} +#endif + +static const char *pemdataraw[] = { + "-----BEGIN RSA PRIVATE KEY-----\n", + "MIIBOgIBAAJBAMFcGsaxxdgiuuGmCkVImy4h99CqT7jwY3pexPGcnUFtR2Fh36Bp\n", + "oncwtkZ4cAgtvd4Qs8PkxUdp6p/DlUmObdkCAwEAAQJAUR44xX6zB3eaeyvTRzms\n", + "kHADrPCmPWnr8dxsNwiDGHzrMKLN+i/HAam+97HxIKVWNDH2ba9Mf1SA8xu9dcHZ\n", + "AQIhAOHPCLxbtQFVxlnhSyxYeb7O323c3QulPNn3bhOipElpAiEA2zZpBE8ZXVnL\n", + "74QjG4zINlDfH+EOEtjJJ3RtaYDugvECIBtsQDxXytChsRgDQ1TcXdStXPcDppie\n", + "dZhm8yhRTTBZAiAZjE/U9rsIDC0ebxIAZfn3iplWh84yGB3pgUI3J5WkoQIhAInE\n", + "HTUY5WRj5riZtkyGnbm3DvF+1eMtO2lYV+OuLcfE\n", + "-----END RSA PRIVATE KEY-----\n", + NULL +}; + +static void test_pem_read_one(void) +{ + EVP_PKEY *key = NULL; + BIO *pem = NULL; + char *pemdata; + size_t len; + + pemdata = glue_strings(pemdataraw, &len); + if (pemdata == NULL) { + multi_set_success(0); + goto err; + } + + pem = BIO_new_mem_buf(pemdata, len); + if (pem == NULL) { + multi_set_success(0); + goto err; + } + + key = PEM_read_bio_PrivateKey(pem, NULL, NULL, NULL); + if (key == NULL) + multi_set_success(0); + + err: + EVP_PKEY_free(key); + BIO_free(pem); + OPENSSL_free(pemdata); +} + +/* Test reading PEM files in multiple threads */ +static int test_pem_read(void) +{ + return thread_run_test(&test_pem_read_one, MAXIMUM_THREADS, + &test_pem_read_one, 1, default_provider); } typedef enum OPTION_choice { OPT_ERR = -1, OPT_EOF = 0, - OPT_FIPS, + OPT_FIPS, OPT_CONFIG_FILE, OPT_TEST_ENUM } OPTION_CHOICE; @@ -513,6 +1195,8 @@ const OPTIONS *test_get_options(void) static const OPTIONS options[] = { OPT_TEST_OPTIONS_DEFAULT_USAGE, { "fips", OPT_FIPS, '-', "Test the FIPS provider" }, + { "config", OPT_CONFIG_FILE, '<', + "The configuration file to use for the libctx" }, { NULL } }; return options; @@ -528,6 +1212,9 @@ int setup_tests(void) case OPT_FIPS: do_fips = 1; break; + case OPT_CONFIG_FILE: + config_file = opt_arg(); + break; case OPT_TEST_CASES: break; default: @@ -542,16 +1229,51 @@ int setup_tests(void) if (!TEST_ptr(privkey)) return 0; + if (!TEST_ptr(global_lock = CRYPTO_THREAD_lock_new())) + return 0; + +#ifdef TSAN_REQUIRES_LOCKING + if (!TEST_ptr(tsan_lock = CRYPTO_THREAD_lock_new())) + return 0; +#endif + + /* Keep first to validate auto creation of default library context */ + ADD_TEST(test_multi_default); + ADD_TEST(test_lock); +#if defined(OPENSSL_THREADS) + ADD_TEST(torture_rw_low); + ADD_TEST(torture_rw_high); +# ifndef OPENSSL_SYS_MACOSX + ADD_TEST(torture_rcu_low); + ADD_TEST(torture_rcu_high); +# endif +#endif ADD_TEST(test_once); ADD_TEST(test_thread_local); ADD_TEST(test_atomic); ADD_TEST(test_multi_load); - ADD_ALL_TESTS(test_multi, 4); + ADD_TEST(test_multi_general_worker_default_provider); + ADD_TEST(test_multi_general_worker_fips_provider); + ADD_TEST(test_multi_fetch_worker); + ADD_TEST(test_multi_shared_pkey); +#ifndef OPENSSL_NO_DEPRECATED_3_0 + ADD_TEST(test_multi_downgrade_shared_pkey); +#endif + ADD_TEST(test_multi_load_unload_provider); + ADD_TEST(test_obj_add); +#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK) + ADD_TEST(test_bio_dgram_pair); +#endif + ADD_TEST(test_pem_read); return 1; } void cleanup_tests(void) { OPENSSL_free(privkey); +#ifdef TSAN_REQUIRES_LOCKING + CRYPTO_THREAD_lock_free(tsan_lock); +#endif + CRYPTO_THREAD_lock_free(global_lock); }