From 4574a7fd8dda070b129d76defca07703cab53842 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C4=8Cestm=C3=ADr=20Kalina?= Date: Mon, 27 Sep 2021 22:42:11 +0200 Subject: [PATCH] crypto: add preemptive threading support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Some primitives are designed to be used in a multi-threaded environment, if supported, e.g., Argon2. This patch adds support for preemptive threading and basic synchronization primitives for platforms compliant with POSIX threads or Windows CRT. Native functions are wrapped to provide a common (internal) API. Threading support can be disabled at compile time. If enabled, threading is disabled by default and needs to be explicitly enabled by the user. Thread enablement requires an explicit limit on the number of threads that OpenSSL may spawn (non-negative integer/infinity). The limit may be changed. Signed-off-by: Čestmír Kalina Reviewed-by: Hugo Landau Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/12255) --- Configure | 19 +- INSTALL.md | 21 ++ crypto/build.info | 2 +- crypto/context.c | 20 ++ crypto/thread/api.c | 73 ++++++ crypto/thread/arch.c | 91 ++++++++ crypto/thread/arch/thread_none.c | 82 +++++++ crypto/thread/arch/thread_posix.c | 305 +++++++++++++++++++++++++ crypto/thread/arch/thread_win.c | 254 +++++++++++++++++++++ crypto/thread/build.info | 8 + crypto/thread/internal.c | 161 ++++++++++++++ doc/man3/CRYPTO_THREAD_run_once.pod | 27 ++- include/crypto/context.h | 6 + include/internal/cryptlib.h | 3 +- include/internal/thread.h | 39 ++++ include/internal/thread_arch.h | 119 ++++++++++ include/openssl/thread.h | 23 ++ test/build.info | 4 +- test/threadstest.c | 333 ++++++++++++++++++++++++++++ util/libcrypto.num | 3 + 20 files changed, 1572 insertions(+), 21 deletions(-) create mode 100644 crypto/thread/api.c create mode 100644 crypto/thread/arch.c create mode 100644 crypto/thread/arch/thread_none.c create mode 100644 crypto/thread/arch/thread_posix.c create mode 100644 crypto/thread/arch/thread_win.c create mode 100644 crypto/thread/build.info create mode 100644 crypto/thread/internal.c create mode 100644 include/internal/thread.h create mode 100644 include/internal/thread_arch.h create mode 100644 include/openssl/thread.h diff --git a/Configure b/Configure index 01c2d0bafe..fbafe0e867 100755 --- a/Configure +++ b/Configure @@ -655,6 +655,9 @@ my @disable_cascades = ( "fips" => [ "fips-securitychecks", "acvp-tests" ], + "threads" => [ "thread-pool" ], + "thread-pool" => [ "default-thread-pool" ], + "deprecated-3.0" => [ "engine", "srp" ] ); @@ -812,8 +815,6 @@ while (@argvcopy) s /^-?-?shared$/enable-shared/; s /^sctp$/enable-sctp/; s /^threads$/enable-threads/; - s /^thread-pool$/enable-thread-pool/; - s /^default-thread-pool$/enable-default-thread-pool/; s /^zlib$/enable-zlib/; s /^zlib-dynamic$/enable-zlib-dynamic/; s /^fips$/enable-fips/; @@ -1400,14 +1401,6 @@ if (grep { $_ =~ /(?:^|\s)-static(?:\s|$)/ } @{$config{LDFLAGS}}) { disable('static', 'pic', 'threads'); } -if ($disabled{threads}) { - disable('unavailable', 'thread-pool'); -} - -if ($disabled{"thread-pool"}) { - disable('unavailable', 'default-thread-pool'); -} - # Allow overriding the build file name $config{build_file} = env('BUILDFILE') || $target{build_file} || "Makefile"; @@ -1506,12 +1499,6 @@ foreach (grep /^-fsanitize=/, @{$config{CFLAGS} || []}) { unless($disabled{threads}) { push @{$config{openssl_feature_defines}}, "OPENSSL_THREADS"; } -unless($disabled{"thread-pool"}) { - push @{$config{openssl_feature_defines}}, "OPENSSL_THREAD_POOL"; -} -unless($disabled{"default-thread-pool"}) { - push @{$config{openssl_feature_defines}}, "OPENSSL_DEFAULT_THREAD_POOL"; -} my $no_shared_warn=0; if (($target{shared_target} // '') eq "") diff --git a/INSTALL.md b/INSTALL.md index 3c995349bd..f16ecf9c89 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -899,6 +899,27 @@ will usually require additional system-dependent options! See [Notes on multi-threading](#notes-on-multi-threading) below. +### no-thread-pool + +Don't build with support for thread pool functionality. + +### thread-pool + +Build with thread pool functionality. If enabled, OpenSSL algorithms may +use the thread pool to perform parallel computation. This option in itself +does not enable OpenSSL to spawn new threads. Currently the only supported +thread pool mechanism is the default thread pool. + +### no-default-thread-pool + +Don't build with support for default thread pool functionality. + +### default-thread-pool + +Build with default thread pool functionality. If enabled, OpenSSL may create +and manage threads up to a maximum number of threads authorized by the +application. Supported on POSIX compliant platforms and Windows. + ### enable-trace Build with support for the integrated tracing api. diff --git a/crypto/build.info b/crypto/build.info index f5b29cca1c..c064351b5a 100644 --- a/crypto/build.info +++ b/crypto/build.info @@ -6,7 +6,7 @@ SUBDIRS=objects buffer bio stack lhash rand evp asn1 pem x509 conf \ siphash sm3 des aes rc2 rc4 rc5 idea aria bf cast camellia \ seed sm4 chacha modes bn ec rsa dsa dh sm2 dso engine \ err comp http ocsp cms ts srp cmac ct async ess crmf cmp encode_decode \ - ffc hpke + ffc hpke thread LIBS=../libcrypto diff --git a/crypto/context.c b/crypto/context.c index aec9ecd4ac..a7b1832cbc 100644 --- a/crypto/context.c +++ b/crypto/context.c @@ -36,6 +36,9 @@ struct ossl_lib_ctx_st { OSSL_METHOD_STORE *encoder_store; OSSL_METHOD_STORE *store_loader_store; void *self_test_cb; +#endif +#if defined(OPENSSL_THREADS) + void *threads; #endif void *rand_crngt; #ifdef FIPS_MODULE @@ -171,6 +174,12 @@ static int context_init(OSSL_LIB_CTX *ctx) goto err; #endif +#if defined(OPENSSL_THREADS) + ctx->threads = ossl_threads_ctx_new(ctx); + if (ctx->threads == NULL) + goto err; +#endif + /* Low priority. */ #ifndef FIPS_MODULE ctx->child_provider = ossl_child_prov_ctx_new(ctx); @@ -299,6 +308,13 @@ static void context_deinit_objs(OSSL_LIB_CTX *ctx) } #endif +#if defined(OPENSSL_THREADS) + if (ctx->threads != NULL) { + ossl_threads_ctx_free(ctx->threads); + ctx->threads = NULL; + } +#endif + /* Low priority. */ #ifndef FIPS_MODULE if (ctx->child_provider != NULL) { @@ -526,6 +542,10 @@ void *ossl_lib_ctx_get_data(OSSL_LIB_CTX *ctx, int index) case OSSL_LIB_CTX_SELF_TEST_CB_INDEX: return ctx->self_test_cb; #endif +#if defined(OPENSSL_THREADS) + case OSSL_LIB_CTX_THREAD_INDEX: + return ctx->threads; +#endif case OSSL_LIB_CTX_RAND_CRNGT_INDEX: { diff --git a/crypto/thread/api.c b/crypto/thread/api.c new file mode 100644 index 0000000000..e025d24cea --- /dev/null +++ b/crypto/thread/api.c @@ -0,0 +1,73 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include +#include + +uint32_t OSSL_get_thread_support_flags(void) +{ + int support = 0; + +#if !defined(OPENSSL_NO_THREAD_POOL) + support |= OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL; +#endif +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + support |= OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN; +#endif + + return support; +} + +#if defined(OPENSSL_NO_THREAD_POOL) || defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + +int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads) +{ + return 0; +} + +uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx) +{ + return 0; +} + +#else + +uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx) +{ + uint64_t ret = 0; + OSSL_LIB_CTX_THREADS *tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + + if (tdata == NULL) + goto fail; + + ossl_crypto_mutex_lock(tdata->lock); + ret = tdata->max_threads; + ossl_crypto_mutex_unlock(tdata->lock); + +fail: + return ret; +} + +int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads) +{ + OSSL_LIB_CTX_THREADS *tdata; + + tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + if (tdata == NULL) + return 0; + + ossl_crypto_mutex_lock(tdata->lock); + tdata->max_threads = max_threads; + ossl_crypto_mutex_unlock(tdata->lock); + + return 1; +} + +#endif diff --git a/crypto/thread/arch.c b/crypto/thread/arch.c new file mode 100644 index 0000000000..565f87b93a --- /dev/null +++ b/crypto/thread/arch.c @@ -0,0 +1,91 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include + +#if defined(OPENSSL_THREADS) + +CRYPTO_THREAD *ossl_crypto_thread_native_start(CRYPTO_THREAD_ROUTINE routine, + void *data, int joinable) +{ + CRYPTO_THREAD *handle; + + if (routine == NULL) + return NULL; + + handle = OPENSSL_zalloc(sizeof(*handle)); + if (handle == NULL) + return NULL; + + if ((handle->lock = ossl_crypto_mutex_new()) == NULL) + goto fail; + if ((handle->statelock = ossl_crypto_mutex_new()) == NULL) + goto fail; + if ((handle->condvar = ossl_crypto_condvar_new()) == NULL) + goto fail; + + handle->data = data; + handle->routine = routine; + handle->joinable = joinable; + + if (ossl_crypto_thread_native_spawn(handle) == 1) + return handle; + +fail: + ossl_crypto_condvar_free(&handle->condvar); + ossl_crypto_mutex_free(&handle->statelock); + ossl_crypto_mutex_free(&handle->lock); + OPENSSL_free(handle); + return NULL; +} + +int ossl_crypto_thread_native_clean(CRYPTO_THREAD *handle) +{ + uint64_t req_state_mask; + + if (handle == NULL) + return 0; + + req_state_mask = 0; + req_state_mask |= CRYPTO_THREAD_FINISHED; + req_state_mask |= CRYPTO_THREAD_TERMINATED; + req_state_mask |= CRYPTO_THREAD_JOINED; + + ossl_crypto_mutex_lock(handle->statelock); + if (CRYPTO_THREAD_GET_STATE(handle, req_state_mask) == 0) { + ossl_crypto_mutex_unlock(handle->statelock); + return 0; + } + ossl_crypto_mutex_unlock(handle->statelock); + + ossl_crypto_mutex_free(&handle->lock); + ossl_crypto_mutex_free(&handle->statelock); + ossl_crypto_condvar_free(&handle->condvar); + + OPENSSL_free(handle->handle); + OPENSSL_free(handle); + + return 1; +} + +#else + +CRYPTO_THREAD *ossl_crypto_thread_native_start(CRYPTO_THREAD_ROUTINE routine, + void *data, int joinable) +{ + return NULL; +} + +int ossl_crypto_thread_native_clean(CRYPTO_THREAD *handle) +{ + return 0; +} + +#endif diff --git a/crypto/thread/arch/thread_none.c b/crypto/thread/arch/thread_none.c new file mode 100644 index 0000000000..8a0389f5cb --- /dev/null +++ b/crypto/thread/arch/thread_none.c @@ -0,0 +1,82 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#if defined(OPENSSL_THREADS_NONE) + +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread) +{ + return 0; +} + +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, CRYPTO_THREAD_RETVAL *retval) +{ + return 0; +} + +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread) +{ + return 0; +} + +int ossl_crypto_thread_native_exit(void) +{ + return 0; +} + +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread) +{ + return 0; +} + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void) +{ + return NULL; +} + +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex) +{ +} + +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex) +{ + return 0; +} + +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex) +{ +} + +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex) +{ +} + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) +{ + return NULL; +} + +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex) +{ +} + +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv) +{ +} + +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv) +{ +} + +void ossl_crypto_mem_barrier(void) +{ +} + +#endif diff --git a/crypto/thread/arch/thread_posix.c b/crypto/thread/arch/thread_posix.c new file mode 100644 index 0000000000..d74cfddab3 --- /dev/null +++ b/crypto/thread/arch/thread_posix.c @@ -0,0 +1,305 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#if defined(OPENSSL_THREADS_POSIX) +# define _GNU_SOURCE +# include +# include +# include + +static void *thread_start_thunk(void *vthread) +{ + CRYPTO_THREAD *thread; + CRYPTO_THREAD_RETVAL ret; + + thread = (CRYPTO_THREAD *)vthread; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + ret = thread->routine(thread->data); + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_FINISHED); + thread->retval = ret; + ossl_crypto_condvar_broadcast(thread->condvar); + ossl_crypto_mutex_unlock(thread->statelock); + + return NULL; +} + +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread) +{ + int ret; + pthread_attr_t attr; + pthread_t *handle; + + handle = OPENSSL_zalloc(sizeof(*handle)); + if (handle == NULL) + goto fail; + + pthread_attr_init(&attr); + if (!thread->joinable) + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ret = pthread_create(handle, &attr, thread_start_thunk, thread); + pthread_attr_destroy(&attr); + + if (ret != 0) + goto fail; + + thread->handle = handle; + return 1; + +fail: + thread->handle = NULL; + OPENSSL_free(handle); + return 0; +} + +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, CRYPTO_THREAD_RETVAL *retval) +{ + void *thread_retval; + pthread_t *handle; + uint64_t req_state_mask; + + if (thread == NULL) + return 0; + + req_state_mask = CRYPTO_THREAD_TERMINATED | CRYPTO_THREAD_JOINED; + + ossl_crypto_mutex_lock(thread->statelock); + if (CRYPTO_THREAD_GET_STATE(thread, req_state_mask)) { + ossl_crypto_mutex_unlock(thread->statelock); + goto pass; + } + while (!CRYPTO_THREAD_GET_STATE(thread, CRYPTO_THREAD_FINISHED)) + ossl_crypto_condvar_wait(thread->condvar, thread->statelock); + ossl_crypto_mutex_unlock(thread->statelock); + + handle = (pthread_t *) thread->handle; + if (handle == NULL) + goto fail; + + if (pthread_join(*handle, &thread_retval) != 0) + goto fail; + + /* + * Join return value may be non-NULL when the thread has been cancelled, + * as indicated by thread_retval set to PTHREAD_CANCELLED. + */ + if (thread_retval != NULL) + goto fail; + +pass: + if (retval != NULL) + *retval = thread->retval; + + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_JOINED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; + +fail: + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; +} + +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread) +{ + void *res; + uint64_t mask; + pthread_t *handle; + + mask = CRYPTO_THREAD_FINISHED; + mask |= CRYPTO_THREAD_TERMINATED; + mask |= CRYPTO_THREAD_JOINED; + + if (thread == NULL) + return 0; + + ossl_crypto_mutex_lock(thread->statelock); + if (thread->handle == NULL || CRYPTO_THREAD_GET_STATE(thread, mask)) + goto terminated; + ossl_crypto_mutex_unlock(thread->statelock); + + handle = thread->handle; + if (pthread_cancel(*handle) != 0) { + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; + } + if (pthread_join(*handle, &res) != 0) + return 0; + if (res != PTHREAD_CANCELED) + return 0; + + thread->handle = NULL; + OPENSSL_free(handle); + + ossl_crypto_mutex_lock(thread->statelock); +terminated: + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; +} + +int ossl_crypto_thread_native_exit(void) +{ + pthread_exit(NULL); + return 1; +} + +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread) +{ + return pthread_equal(*(pthread_t *)thread->handle, pthread_self()); +} + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void) +{ + pthread_mutex_t *mutex; + + if ((mutex = OPENSSL_zalloc(sizeof(*mutex))) == NULL) + return NULL; + if (pthread_mutex_init(mutex, NULL) != 0) { + OPENSSL_free(mutex); + return NULL; + } + return (CRYPTO_MUTEX *)mutex; +} + +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex) +{ + pthread_mutex_t *mutex_p; + + mutex_p = (pthread_mutex_t *)mutex; + + if (pthread_mutex_trylock(mutex_p) == EBUSY) + return 0; + + return 1; +} + +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex) +{ + pthread_mutex_t *mutex_p; + + mutex_p = (pthread_mutex_t *)mutex; + pthread_mutex_lock(mutex_p); +} + +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex) +{ + pthread_mutex_t *mutex_p; + + mutex_p = (pthread_mutex_t *)mutex; + pthread_mutex_unlock(mutex_p); +} + +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex) +{ + pthread_mutex_t **mutex_p; + + if (mutex == NULL) + return; + + mutex_p = (pthread_mutex_t **)mutex; + if (*mutex_p != NULL) + pthread_mutex_destroy(*mutex_p); + OPENSSL_free(*mutex_p); + *mutex = NULL; +} + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) +{ + pthread_cond_t *cv_p; + + if ((cv_p = OPENSSL_zalloc(sizeof(*cv_p))) == NULL) + return NULL; + if (pthread_cond_init(cv_p, NULL) != 0) { + OPENSSL_free(cv_p); + return NULL; + } + return (CRYPTO_CONDVAR *) cv_p; +} + +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex) +{ + pthread_cond_t *cv_p; + pthread_mutex_t *mutex_p; + + cv_p = (pthread_cond_t *)cv; + mutex_p = (pthread_mutex_t *)mutex; + pthread_cond_wait(cv_p, mutex_p); +} + +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv) +{ + pthread_cond_t *cv_p; + + cv_p = (pthread_cond_t *)cv; + pthread_cond_broadcast(cv_p); +} + +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv) +{ + pthread_cond_t **cv_p; + + if (cv == NULL) + return; + + cv_p = (pthread_cond_t **)cv; + if (*cv_p != NULL) + pthread_cond_destroy(*cv_p); + OPENSSL_free(*cv_p); + *cv_p = NULL; +} + +void ossl_crypto_mem_barrier(void) +{ +# if defined(__clang__) || defined(__GNUC__) + __sync_synchronize(); +# elif !defined(OPENSSL_NO_ASM) +# if defined(__alpha__) /* Alpha */ + __asm__ volatile("mb" : : : "memory"); +# elif defined(__amd64__) || defined(__i386__) || defined(__i486__) \ + || defined(__i586__) || defined(__i686__) || defined(__i386) /* x86 */ + __asm__ volatile("mfence" : : : "memory"); +# elif defined(__arm__) || defined(__aarch64__) /* ARMv7, ARMv8 */ + __asm__ volatile("dmb ish" : : : "memory"); +# elif defined(__hppa__) /* PARISC */ + __asm__ volatile("" : : : "memory"); +# elif defined(__mips__) /* MIPS */ + __asm__ volatile("sync" : : : "memory"); +# elif defined(__powerpc__) || defined(__powerpc64__) /* power, ppc64, ppc64le */ + __asm__ volatile("sync" : : : "memory"); +# elif defined(__sparc__) + __asm__ volatile("ba,pt %%xcc, 1f\n\t" \ + " membar #Sync\n" \ + "1:\n" \ + : : : "memory"); +# elif defined(__s390__) || defined(__s390x__) /* z */ + __asm__ volatile("bcr 15,0" : : : "memory"); +# elif defined(__riscv) || defined(__riscv__) /* riscv */ + __asm__ volatile("fence iorw,iorw" : : : "memory"); +# else /* others, compiler only */ + __asm__ volatile("" : : : "memory"); +# endif +# else + /* compiler only barrier */ + __asm__ volatile("" : : : "memory"); +# endif +} + +#endif diff --git a/crypto/thread/arch/thread_win.c b/crypto/thread/arch/thread_win.c new file mode 100644 index 0000000000..b71cda85ea --- /dev/null +++ b/crypto/thread/arch/thread_win.c @@ -0,0 +1,254 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#if defined(OPENSSL_THREADS_WINNT) +# include +# include + +static DWORD __stdcall thread_start_thunk(LPVOID vthread) +{ + CRYPTO_THREAD *thread; + CRYPTO_THREAD_RETVAL ret; + + thread = (CRYPTO_THREAD *)vthread; + + thread->thread_id = GetCurrentThreadId(); + + ret = thread->routine(thread->data); + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_FINISHED); + thread->retval = ret; + ossl_crypto_condvar_broadcast(thread->condvar); + ossl_crypto_mutex_unlock(thread->statelock); + + return 0; +} + +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread) +{ + HANDLE *handle; + + handle = OPENSSL_zalloc(sizeof(*handle)); + if (handle == NULL) + goto fail; + + *handle = (HANDLE)_beginthreadex(NULL, 0, &thread_start_thunk, thread, 0, NULL); + if (*handle == NULL) + goto fail; + + thread->handle = handle; + return 1; + +fail: + thread->handle = NULL; + OPENSSL_free(handle); + return 0; +} + +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, CRYPTO_THREAD_RETVAL *retval) +{ + int req_state_mask; + DWORD thread_retval; + HANDLE *handle; + + if (thread == NULL) + return 0; + + req_state_mask = CRYPTO_THREAD_TERMINATED | CRYPTO_THREAD_JOINED; + + ossl_crypto_mutex_lock(thread->statelock); + if (CRYPTO_THREAD_GET_STATE(thread, req_state_mask)) + goto pass; + while (!CRYPTO_THREAD_GET_STATE(thread, CRYPTO_THREAD_FINISHED)) + ossl_crypto_condvar_wait(thread->condvar, thread->statelock); + + handle = (HANDLE *) thread->handle; + if (handle == NULL) + goto fail; + + if (WaitForSingleObject(*handle, INFINITE) != WAIT_OBJECT_0) + goto fail; + + if (GetExitCodeThread(*handle, &thread_retval) == 0) + goto fail; + + /* + * GetExitCodeThread call followed by this check is to make sure that + * the thread exitted properly. In particular, thread_retval may be + * non-zero when exitted via explicit ExitThread/TerminateThread or + * if the thread is still active (returns STILL_ACTIVE (259)). + */ + if (thread_retval != 0) + goto fail; + + if (CloseHandle(*handle) == 0) + goto fail; + +pass: + if (retval != NULL) + *retval = thread->retval; + + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_JOINED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; + +fail: + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; +} + +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread) +{ + uint64_t mask; + HANDLE *handle; + + mask = CRYPTO_THREAD_FINISHED; + mask |= CRYPTO_THREAD_TERMINATED; + mask |= CRYPTO_THREAD_JOINED; + + if (thread == NULL) + return 1; + + ossl_crypto_mutex_lock(thread->statelock); + if (thread->handle == NULL || CRYPTO_THREAD_GET_STATE(thread, mask)) + goto terminated; + ossl_crypto_mutex_unlock(thread->statelock); + + handle = thread->handle; + if (WaitForSingleObject(*handle, 0) != WAIT_OBJECT_0) { + if (TerminateThread(*handle, STILL_ACTIVE) == 0) { + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; + } + } + + if (CloseHandle(*handle) == 0) { + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + return 0; + } + + thread->handle = NULL; + OPENSSL_free(handle); + + ossl_crypto_mutex_lock(thread->statelock); +terminated: + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; +} + +int ossl_crypto_thread_native_exit(void) +{ + _endthreadex(0); + return 1; +} + +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread) +{ + return thread->thread_id == GetCurrentThreadId(); +} + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void) +{ + CRITICAL_SECTION *mutex; + + if ((mutex = OPENSSL_zalloc(sizeof(*mutex))) == NULL) + return NULL; + InitializeCriticalSection(mutex); + return (CRYPTO_MUTEX *)mutex; +} + +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex) +{ + CRITICAL_SECTION *mutex_p; + + mutex_p = (CRITICAL_SECTION *)mutex; + EnterCriticalSection(mutex_p); +} + +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex) +{ + CRITICAL_SECTION *mutex_p; + + mutex_p = (CRITICAL_SECTION *)mutex; + if (TryEnterCriticalSection(mutex_p)) + return 1; + + return 0; +} + +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex) +{ + CRITICAL_SECTION *mutex_p; + + mutex_p = (CRITICAL_SECTION *)mutex; + LeaveCriticalSection(mutex_p); +} + +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex) +{ + CRITICAL_SECTION **mutex_p; + + mutex_p = (CRITICAL_SECTION **)mutex; + if (*mutex_p != NULL) + DeleteCriticalSection(*mutex_p); + OPENSSL_free(*mutex_p); + *mutex = NULL; +} + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) +{ + CONDITION_VARIABLE *cv_p; + + if ((cv_p = OPENSSL_zalloc(sizeof(*cv_p))) == NULL) + return NULL; + InitializeConditionVariable(cv_p); + return (CRYPTO_CONDVAR *)cv_p; +} + +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex) +{ + CONDITION_VARIABLE *cv_p; + CRITICAL_SECTION *mutex_p; + + cv_p = (CONDITION_VARIABLE *)cv; + mutex_p = (CRITICAL_SECTION *)mutex; + SleepConditionVariableCS(cv_p, mutex_p, INFINITE); +} + +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv) +{ + CONDITION_VARIABLE *cv_p; + + cv_p = (CONDITION_VARIABLE *)cv; + WakeAllConditionVariable(cv_p); +} + +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv) +{ + CONDITION_VARIABLE **cv_p; + + cv_p = (CONDITION_VARIABLE **)cv; + OPENSSL_free(*cv_p); + *cv_p = NULL; +} + +void ossl_crypto_mem_barrier(void) +{ + MemoryBarrier(); +} + +#endif diff --git a/crypto/thread/build.info b/crypto/thread/build.info new file mode 100644 index 0000000000..3ab689d4a4 --- /dev/null +++ b/crypto/thread/build.info @@ -0,0 +1,8 @@ +LIBS=../../libcrypto + +$THREADS=\ + api.c internal.c arch.c \ + arch/thread_win.c arch/thread_posix.c arch/thread_none.c + +SOURCE[../../libcrypto]=$THREADS +SOURCE[../../providers/libfips.a]=$THREADS diff --git a/crypto/thread/internal.c b/crypto/thread/internal.c new file mode 100644 index 0000000000..22af876cd7 --- /dev/null +++ b/crypto/thread/internal.c @@ -0,0 +1,161 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include +#include +#include +#include +#include + +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + +static ossl_inline uint64_t _ossl_get_avail_threads(OSSL_LIB_CTX_THREADS *tdata) +{ + /* assumes that tdata->lock is taken */ + return tdata->max_threads - tdata->active_threads; +} + +uint64_t ossl_get_avail_threads(OSSL_LIB_CTX *ctx) +{ + uint64_t retval = 0; + OSSL_LIB_CTX_THREADS *tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + + if (tdata == NULL) + return retval; + + ossl_crypto_mutex_lock(tdata->lock); + retval = _ossl_get_avail_threads(tdata); + ossl_crypto_mutex_unlock(tdata->lock); + + return retval; +} + +void *ossl_crypto_thread_start(OSSL_LIB_CTX *ctx, CRYPTO_THREAD_ROUTINE start, + void *data) +{ + CRYPTO_THREAD *thread; + OSSL_LIB_CTX_THREADS *tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + + if (tdata == NULL) + return NULL; + + ossl_crypto_mutex_lock(tdata->lock); + if (tdata == NULL || tdata->max_threads == 0) { + ossl_crypto_mutex_unlock(tdata->lock); + return NULL; + } + + while (_ossl_get_avail_threads(tdata) == 0) + ossl_crypto_condvar_wait(tdata->cond_finished, tdata->lock); + tdata->active_threads++; + ossl_crypto_mutex_unlock(tdata->lock); + + thread = ossl_crypto_thread_native_start(start, data, 1); + if (thread == NULL) { + ossl_crypto_mutex_lock(tdata->lock); + tdata->active_threads--; + ossl_crypto_mutex_unlock(tdata->lock); + goto fail; + } + thread->ctx = ctx; + +fail: + return (void *) thread; +} + +int ossl_crypto_thread_join(void *vhandle, CRYPTO_THREAD_RETVAL *retval) +{ + CRYPTO_THREAD *handle = vhandle; + OSSL_LIB_CTX_THREADS *tdata; + + if (vhandle == NULL) + return 0; + + tdata = OSSL_LIB_CTX_GET_THREADS(handle->ctx); + if (tdata == NULL) + return 0; + + if (ossl_crypto_thread_native_join(handle, retval) == 0) + return 0; + + ossl_crypto_mutex_lock(tdata->lock); + tdata->active_threads--; + ossl_crypto_condvar_broadcast(tdata->cond_finished); + ossl_crypto_mutex_unlock(tdata->lock); + return 1; +} + +int ossl_crypto_thread_clean(void *vhandle) +{ + CRYPTO_THREAD *handle = vhandle; + + return ossl_crypto_thread_native_clean(handle); +} + +#else + +ossl_inline uint64_t ossl_get_avail_threads(OSSL_LIB_CTX *ctx) +{ + return 0; +} + +void *ossl_crypto_thread_start(OSSL_LIB_CTX *ctx, CRYPTO_THREAD_ROUTINE start, + void *data) +{ + return NULL; +} + +int ossl_crypto_thread_join(void *vhandle, CRYPTO_THREAD_RETVAL *retval) +{ + return 0; +} + +int ossl_crypto_thread_clean(void *vhandle) +{ + return 0; +} + +#endif + +#if defined(OPENSSL_THREADS) + +void *ossl_threads_ctx_new(OSSL_LIB_CTX *ctx) +{ + struct openssl_threads_st *t = OPENSSL_zalloc(sizeof(*t)); + + if (t == NULL) + return NULL; + + t->lock = ossl_crypto_mutex_new(); + t->cond_finished = ossl_crypto_condvar_new(); + + if (t->lock == NULL || t->cond_finished == NULL) + goto fail; + + return t; + +fail: + ossl_threads_ctx_free((void *)t); + return NULL; +} + +void ossl_threads_ctx_free(void *vdata) +{ + OSSL_LIB_CTX_THREADS *t = (OSSL_LIB_CTX_THREADS *) vdata; + + if (t == NULL) + return; + + ossl_crypto_mutex_free(&t->lock); + ossl_crypto_condvar_free(&t->cond_finished); + OPENSSL_free(t); +} + +#endif diff --git a/doc/man3/CRYPTO_THREAD_run_once.pod b/doc/man3/CRYPTO_THREAD_run_once.pod index a51679b97e..fd2d6a207f 100644 --- a/doc/man3/CRYPTO_THREAD_run_once.pod +++ b/doc/man3/CRYPTO_THREAD_run_once.pod @@ -5,7 +5,9 @@ CRYPTO_THREAD_run_once, CRYPTO_THREAD_lock_new, CRYPTO_THREAD_read_lock, CRYPTO_THREAD_write_lock, CRYPTO_THREAD_unlock, CRYPTO_THREAD_lock_free, -CRYPTO_atomic_add, CRYPTO_atomic_or, CRYPTO_atomic_load - OpenSSL thread support +CRYPTO_atomic_add, CRYPTO_atomic_or, CRYPTO_atomic_load, +OSSL_set_max_threads, OSSL_get_max_threads, +OSSL_get_thread_support_flags - OpenSSL thread support =head1 SYNOPSIS @@ -25,6 +27,10 @@ CRYPTO_atomic_add, CRYPTO_atomic_or, CRYPTO_atomic_load - OpenSSL thread support CRYPTO_RWLOCK *lock); int CRYPTO_atomic_load(uint64_t *val, uint64_t *ret, CRYPTO_RWLOCK *lock); + int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads); + uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx); + uint32_t OSSL_get_thread_support_flags(void); + =head1 DESCRIPTION OpenSSL can be safely used in multi-threaded applications provided that @@ -98,6 +104,16 @@ read by CRYPTO_atomic_load() then CRYPTO_atomic_load() must be the only way that the variable is read. If atomic operations are not supported and I is NULL, then the function will fail. +=item * + +OSSL_set_max_threads() sets the maximum number of threads to be used by the +thread pool. If the argument is 0, thread pooling is disabled. OpenSSL will +not create any threads and existing threads in the thread pool will be torn +down. The maximum thread count is a limit, not a target. Threads will not be +spawned unless (and until) there is demand. Thread polling is disabled by +default. To enable threading you must call OSSL_set_max_threads() explicitly. +Under no circumstances is this done for you. + =back =head1 RETURN VALUES @@ -108,6 +124,15 @@ CRYPTO_THREAD_lock_new() returns the allocated lock, or NULL on error. CRYPTO_THREAD_lock_free() returns no value. +OSSL_set_max_threads() returns 1 on success and 0 on failure. Returns failure +if OpenSSL-managed thread pooling is not supported (for example, if it is not +supported on the current platform, or because OpenSSL is not built with the +necessary support). + +OSSL_get_max_threads() returns the maximum number of threads currently allowed +to be used by the thread pool. If thread pooling is disabled or not available, +returns 0. + The other functions return 1 on success, or 0 on error. =head1 NOTES diff --git a/include/crypto/context.h b/include/crypto/context.h index 143f6d6b6d..950d6f11e4 100644 --- a/include/crypto/context.h +++ b/include/crypto/context.h @@ -23,6 +23,9 @@ void *ossl_self_test_set_callback_new(OSSL_LIB_CTX *); void *ossl_rand_crng_ctx_new(OSSL_LIB_CTX *); void *ossl_thread_event_ctx_new(OSSL_LIB_CTX *); void *ossl_fips_prov_ossl_ctx_new(OSSL_LIB_CTX *); +#if defined(OPENSSL_THREADS) +void *ossl_threads_ctx_new(OSSL_LIB_CTX *); +#endif void ossl_provider_store_free(void *); void ossl_property_string_data_free(void *); @@ -38,3 +41,6 @@ void ossl_self_test_set_callback_free(void *); void ossl_rand_crng_ctx_free(void *); void ossl_thread_event_ctx_free(void *); void ossl_fips_prov_ossl_ctx_free(void *); +#if defined(OPENSSL_THREADS) +void ossl_threads_ctx_free(void *); +#endif diff --git a/include/internal/cryptlib.h b/include/internal/cryptlib.h index 71b6b125f3..700f387531 100644 --- a/include/internal/cryptlib.h +++ b/include/internal/cryptlib.h @@ -116,7 +116,8 @@ typedef struct ossl_ex_data_global_st { # define OSSL_LIB_CTX_PROVIDER_CONF_INDEX 16 # define OSSL_LIB_CTX_BIO_CORE_INDEX 17 # define OSSL_LIB_CTX_CHILD_PROVIDER_INDEX 18 -# define OSSL_LIB_CTX_MAX_INDEXES 19 +# define OSSL_LIB_CTX_THREAD_INDEX 19 +# define OSSL_LIB_CTX_MAX_INDEXES 20 OSSL_LIB_CTX *ossl_lib_ctx_get_concrete(OSSL_LIB_CTX *ctx); int ossl_lib_ctx_is_default(OSSL_LIB_CTX *ctx); diff --git a/include/internal/thread.h b/include/internal/thread.h new file mode 100644 index 0000000000..8c5bad7763 --- /dev/null +++ b/include/internal/thread.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OPENSSL_INTERNAL_THREAD_H +# define OPENSSL_INTERNAL_THREAD_H +# include +# include +# include +# include +# include +# include "crypto/context.h" + +void *ossl_crypto_thread_start(OSSL_LIB_CTX *ctx, CRYPTO_THREAD_ROUTINE start, + void *data); +int ossl_crypto_thread_join(void *task, CRYPTO_THREAD_RETVAL *retval); +int ossl_crypto_thread_clean(void *vhandle); +uint64_t ossl_get_avail_threads(OSSL_LIB_CTX *ctx); + +# if defined(OPENSSL_THREADS) + +# define OSSL_LIB_CTX_GET_THREADS(CTX) \ + ossl_lib_ctx_get_data(CTX, OSSL_LIB_CTX_THREAD_INDEX); + +typedef struct openssl_threads_st { + uint64_t max_threads; + uint64_t active_threads; + CRYPTO_MUTEX *lock; + CRYPTO_CONDVAR *cond_finished; +} OSSL_LIB_CTX_THREADS; + +# endif /* defined(OPENSSL_THREADS) */ + +#endif /* OPENSSL_INTERNAL_THREAD_H */ diff --git a/include/internal/thread_arch.h b/include/internal/thread_arch.h new file mode 100644 index 0000000000..fcf312ff14 --- /dev/null +++ b/include/internal/thread_arch.h @@ -0,0 +1,119 @@ +/* + * Copyright 2019-2021 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OSSL_INTERNAL_THREAD_ARCH_H +# define OSSL_INTERNAL_THREAD_ARCH_H +# include +# include + +# if defined(_WIN32) +# include +# endif + +# if defined(OPENSSL_THREADS) && defined(OPENSSL_SYS_UNIX) +# define OPENSSL_THREADS_POSIX +# elif defined(OPENSSL_THREADS) && defined(OPENSSL_SYS_WINDOWS) && \ + defined(_WIN32_WINNT) +# if _WIN32_WINNT >= 0x0600 +# define OPENSSL_THREADS_WINNT +# else +# define OPENSSL_THREADS_NONE +# endif +# else +# define OPENSSL_THREADS_NONE +# endif + +# include + +typedef void CRYPTO_MUTEX; +typedef void CRYPTO_CONDVAR; + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void); +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex); +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex); +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex); +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex); + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void); +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex); +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv); +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv); + +typedef uint32_t CRYPTO_THREAD_RETVAL; +typedef CRYPTO_THREAD_RETVAL (*CRYPTO_THREAD_ROUTINE)(void *); +typedef CRYPTO_THREAD_RETVAL (*CRYPTO_THREAD_ROUTINE_CB)(void *, + void (**)(void *), + void **); + +# define CRYPTO_THREAD_NO_STATE 0UL +# define CRYPTO_THREAD_FINISHED (1UL << 1) +# define CRYPTO_THREAD_JOINED (1UL << 2) +# define CRYPTO_THREAD_TERMINATED (1UL << 3) + +# define CRYPTO_THREAD_GET_STATE(THREAD, FLAG) ((THREAD)->state & (FLAG)) +# define CRYPTO_THREAD_GET_ERROR(THREAD, FLAG) (((THREAD)->state >> 16) & (FLAG)) + +typedef struct crypto_thread_st { + uint32_t state; + void *data; + CRYPTO_THREAD_ROUTINE routine; + CRYPTO_THREAD_RETVAL retval; + void *handle; + CRYPTO_MUTEX *lock; + CRYPTO_MUTEX *statelock; + CRYPTO_CONDVAR *condvar; + unsigned long thread_id; + int joinable; + OSSL_LIB_CTX *ctx; +} CRYPTO_THREAD; + +# if defined(OPENSSL_THREADS) + +# define CRYPTO_THREAD_UNSET_STATE(THREAD, FLAG) \ + do { \ + (THREAD)->state &= ~(FLAG); \ + } while ((void)0, 0) + +# define CRYPTO_THREAD_SET_STATE(THREAD, FLAG) \ + do { \ + (THREAD)->state |= (FLAG); \ + } while ((void)0, 0) + +# define CRYPTO_THREAD_SET_ERROR(THREAD, FLAG) \ + do { \ + (THREAD)->state |= ((FLAG) << 16); \ + } while ((void)0, 0) + +# define CRYPTO_THREAD_UNSET_ERROR(THREAD, FLAG) \ + do { \ + (THREAD)->state &= ~((FLAG) << 16); \ + } while ((void)0, 0) + +# else + +# define CRYPTO_THREAD_UNSET_STATE(THREAD, FLAG) +# define CRYPTO_THREAD_SET_STATE(THREAD, FLAG) +# define CRYPTO_THREAD_SET_ERROR(THREAD, FLAG) +# define CRYPTO_THREAD_UNSET_ERROR(THREAD, FLAG) + +# endif /* defined(OPENSSL_THREADS) */ + +CRYPTO_THREAD * ossl_crypto_thread_native_start(CRYPTO_THREAD_ROUTINE routine, + void *data, int joinable); +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread); +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, + CRYPTO_THREAD_RETVAL *retval); +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread); +int ossl_crypto_thread_native_exit(void); +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread); +int ossl_crypto_thread_native_clean(CRYPTO_THREAD *thread); + +void ossl_crypto_mem_barrier(void); + +#endif /* OSSL_INTERNAL_THREAD_ARCH_H */ diff --git a/include/openssl/thread.h b/include/openssl/thread.h new file mode 100644 index 0000000000..68ecf9c4c4 --- /dev/null +++ b/include/openssl/thread.h @@ -0,0 +1,23 @@ +/* + * Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. 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 + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OPENSSL_THREAD_H +# define OPENSSL_THREAD_H + +# define OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL (1U<<0) +# define OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN (1U<<1) + +# include + +uint32_t OSSL_get_thread_support_flags(void); +int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads); +uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx); + +#endif /* OPENSSL_THREAD_H */ diff --git a/test/build.info b/test/build.info index 6caaa618cf..e2cfddb222 100644 --- a/test/build.info +++ b/test/build.info @@ -340,8 +340,8 @@ IF[{- !$disabled{tests} -}] DEPEND[ct_test]=../libcrypto libtestutil.a SOURCE[threadstest]=threadstest.c - INCLUDE[threadstest]=../include ../apps/include - DEPEND[threadstest]=../libcrypto libtestutil.a + INCLUDE[threadstest]=.. ../include ../apps/include + DEPEND[threadstest]=../libcrypto.a libtestutil.a SOURCE[threadstest_fips]=threadstest_fips.c INCLUDE[threadstest_fips]=../include ../apps/include diff --git a/test/threadstest.c b/test/threadstest.c index 505b2cf07e..af1d78a3b7 100644 --- a/test/threadstest.c +++ b/test/threadstest.c @@ -20,11 +20,15 @@ #endif #include +#include +#include +#include #include #include #include #include #include +#include #include "internal/tsan_assist.h" #include "internal/nelem.h" #include "testutil.h" @@ -741,6 +745,326 @@ err: } #endif +static int test_thread_reported_flags(void) +{ + uint32_t flags = OSSL_get_thread_support_flags(); + +#if !defined(OPENSSL_THREADS) + if (!TEST_int_eq(flags, 0)) + return 0; +#endif + +#if defined(OPENSSL_NO_THREAD_POOL) + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL, 0)) + return 0; +#else + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL, + OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL)) + return 0; +#endif + +#if defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN, 0)) + return 0; +#else + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN, + OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN)) + return 0; +#endif + + return 1; +} + +#if defined(OPENSSL_THREADS) + +# define TEST_THREAD_NATIVE_FN_SET_VALUE 1 +static uint32_t test_thread_native_fn(void *data) +{ + uint32_t *ldata = (uint32_t*) data; + *ldata = *ldata + 1; + return *ldata - 1; +} + +static uint32_t test_thread_noreturn(void *data) +{ + CRYPTO_MUTEX *lock = (uint32_t*) data; + + /* lock is assumed to be locked */ + ossl_crypto_mutex_lock(lock); + + /* unreachable */ + OPENSSL_die("test_thread_noreturn", __FILE__, __LINE__); + return 0; +} + +/* Tests of native threads */ + +static int test_thread_native(void) +{ + int testval = 0; + uint32_t retval; + uint32_t local; + CRYPTO_THREAD *t; + CRYPTO_MUTEX *lock; + + /* thread spawn, join and termination */ + + local = 1; + t = ossl_crypto_thread_native_start(test_thread_native_fn, &local, 1); + if (!TEST_ptr(t)) + return 0; + + /* + * pthread_join results in undefined behaviour if called on a joined + * thread. We do not impose such restrictions, so it's up to us to + * ensure that this does not happen (thread sanitizer will warn us + * if we do). + */ + if (!TEST_int_eq(ossl_crypto_thread_native_join(t, &retval), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_native_join(t, &retval), 1)) + return 0; + + if (!TEST_int_eq(retval, 1) || !TEST_int_eq(local, 2)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_terminate(t), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_native_terminate(t), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_join(t, &retval), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 1)) + return 0; + t = NULL; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 0)) + return 0; + + /* termination of a long running thread */ + + lock = ossl_crypto_mutex_new(); + if (!TEST_ptr(lock)) + return 0; + ossl_crypto_mutex_lock(lock); + + t = ossl_crypto_thread_native_start(test_thread_noreturn, lock, 1); + if (!TEST_ptr(t)) + goto fail; + if (!TEST_int_eq(ossl_crypto_thread_native_terminate(t), 1)) + goto fail; + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 1)) + goto fail; + + testval = 1; + +fail: + ossl_crypto_mutex_unlock(lock); + ossl_crypto_mutex_free(&lock); + if (!TEST_ptr_null(lock)) + return 0; + + return testval; +} + +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) +static int test_thread_internal(void) +{ + uint32_t retval[3]; + uint32_t local[3] = { 0 }; + uint32_t threads_supported; + size_t i; + void *t[3]; + OSSL_LIB_CTX *cust_ctx = OSSL_LIB_CTX_new(); + + threads_supported = OSSL_get_thread_support_flags(); + threads_supported &= OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN; + + if (threads_supported == 0) { + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, 1), 0)) + return 0; + if (!TEST_int_eq(OSSL_set_max_threads(cust_ctx, 1), 0)) + return 0; + + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + + t[0] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr_null(t[0])) + return 0; + + return 1; + } + + /* fail when not allowed to use threads */ + + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + t[0] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr_null(t[0])) + return 0; + + /* fail when enabled on a different context */ + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + if (!TEST_int_eq(OSSL_set_max_threads(cust_ctx, 1), 1)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 1)) + return 0; + t[0] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr_null(t[0])) + return 0; + if (!TEST_int_eq(OSSL_set_max_threads(cust_ctx, 0), 1)) + return 0; + + /* sequential startup */ + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, 1), 1)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 1)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + + for (i = 0; i < OSSL_NELEM(t); ++i) { + local[0] = i + 1; + + t[i] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr(t[i])) + return 0; + + /* + * pthread_join results in undefined behaviour if called on a joined + * thread. We do not impose such restrictions, so it's up to us to + * ensure that this does not happen (thread sanitizer will warn us + * if we do). + */ + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[0]), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[0]), 1)) + return 0; + + if (!TEST_int_eq(retval[0], i + 1) || !TEST_int_eq(local[0], i + 2)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 1)) + return 0; + t[i] = NULL; + + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 0)) + return 0; + } + + /* parallel startup */ + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, OSSL_NELEM(t)), 1)) + return 0; + + for (i = 0; i < OSSL_NELEM(t); ++i) { + local[i] = i + 1; + t[i] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[i]); + if (!TEST_ptr(t[i])) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[i]), 1)) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(retval[i], i + 1) || !TEST_int_eq(local[i], i + 2)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 1)) + return 0; + } + + /* parallel startup, bottleneck */ + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, OSSL_NELEM(t) - 1), 1)) + return 0; + + for (i = 0; i < OSSL_NELEM(t); ++i) { + local[i] = i + 1; + t[i] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[i]); + if (!TEST_ptr(t[i])) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[i]), 1)) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(retval[i], i + 1) || !TEST_int_eq(local[i], i + 2)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 1)) + return 0; + } + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, 0), 1)) + return 0; + + OSSL_LIB_CTX_free(cust_ctx); + return 1; +} +#endif + +static uint32_t test_thread_native_multiple_joins_fn1(void *data) +{ + return 0; +} + +static uint32_t test_thread_native_multiple_joins_fn2(void *data) +{ + ossl_crypto_thread_native_join((CRYPTO_THREAD *)data, NULL); + return 0; +} + +static uint32_t test_thread_native_multiple_joins_fn3(void *data) +{ + ossl_crypto_thread_native_join((CRYPTO_THREAD *)data, NULL); + return 0; +} + +static int test_thread_native_multiple_joins(void) +{ + CRYPTO_THREAD *t, *t1, *t2; + + t = ossl_crypto_thread_native_start(test_thread_native_multiple_joins_fn1, NULL, 1); + t1 = ossl_crypto_thread_native_start(test_thread_native_multiple_joins_fn2, t, 1); + t2 = ossl_crypto_thread_native_start(test_thread_native_multiple_joins_fn3, t, 1); + + if (!TEST_ptr(t) || !TEST_ptr(t1) || !TEST_ptr(t2)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_join(t2, NULL), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_native_join(t1, NULL), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t2), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t1), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 1)) + return 0; + + return 1; +} + +#endif + typedef enum OPTION_choice { OPT_ERR = -1, OPT_EOF = 0, @@ -816,6 +1140,15 @@ int setup_tests(void) #if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK) ADD_TEST(test_bio_dgram_pair); #endif + ADD_TEST(test_thread_reported_flags); +#if defined(OPENSSL_THREADS) + ADD_TEST(test_thread_native); + ADD_TEST(test_thread_native_multiple_joins); +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + ADD_TEST(test_thread_internal); +#endif +#endif + return 1; } diff --git a/util/libcrypto.num b/util/libcrypto.num index ba44622210..f5951d59e5 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5466,3 +5466,6 @@ EVP_PKEY_auth_decapsulate_init ? 3_2_0 EXIST::FUNCTION: PKCS12_SAFEBAG_set0_attrs ? 3_2_0 EXIST::FUNCTION: PKCS12_create_ex2 ? 3_2_0 EXIST::FUNCTION: OSSL_sleep ? 3_2_0 EXIST::FUNCTION: +OSSL_get_thread_support_flags ? 3_2_0 EXIST::FUNCTION: +OSSL_set_max_threads ? 3_2_0 EXIST::FUNCTION: +OSSL_get_max_threads ? 3_2_0 EXIST::FUNCTION: -- 2.39.5