crypto: add preemptive threading support
authorČestmír Kalina <ckalina@redhat.com>
Mon, 27 Sep 2021 20:42:11 +0000 (22:42 +0200)
committerMatt Caswell <matt@openssl.org>
Mon, 17 Oct 2022 08:45:39 +0000 (09:45 +0100)
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 <ckalina@redhat.com>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/12255)

20 files changed:
Configure
INSTALL.md
crypto/build.info
crypto/context.c
crypto/thread/api.c [new file with mode: 0644]
crypto/thread/arch.c [new file with mode: 0644]
crypto/thread/arch/thread_none.c [new file with mode: 0644]
crypto/thread/arch/thread_posix.c [new file with mode: 0644]
crypto/thread/arch/thread_win.c [new file with mode: 0644]
crypto/thread/build.info [new file with mode: 0644]
crypto/thread/internal.c [new file with mode: 0644]
doc/man3/CRYPTO_THREAD_run_once.pod
include/crypto/context.h
include/internal/cryptlib.h
include/internal/thread.h [new file with mode: 0644]
include/internal/thread_arch.h [new file with mode: 0644]
include/openssl/thread.h [new file with mode: 0644]
test/build.info
test/threadstest.c
util/libcrypto.num

index 01c2d0bafede3d9213ff51bb5a8f054f2d113d40..fbafe0e867c9b2add7a80e9a2ab25af9d509c795 100755 (executable)
--- 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 "")
index 3c995349bd3b17d1ede4273832ad0b6bdee92bf7..f16ecf9c8993795bf9aa1ab49fc518630e5ae101 100644 (file)
@@ -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.
index f5b29cca1cae2c56f476c03619955aff185e1e1c..c064351b5aeae97a4a698d6d1a5bf0744093da79 100644 (file)
@@ -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
 
index aec9ecd4acdc4e7030032cb96c24664b2471a53f..a7b1832cbcf96ac6299dddedb6e37ed227faf088 100644 (file)
@@ -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 (file)
index 0000000..e025d24
--- /dev/null
@@ -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 <openssl/configuration.h>
+#include <openssl/thread.h>
+#include <internal/thread.h>
+
+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 (file)
index 0000000..565f87b
--- /dev/null
@@ -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 <openssl/configuration.h>
+#include <internal/thread_arch.h>
+
+#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 (file)
index 0000000..8a0389f
--- /dev/null
@@ -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 <internal/thread_arch.h>
+
+#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 (file)
index 0000000..d74cfdd
--- /dev/null
@@ -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 <internal/thread_arch.h>
+
+#if defined(OPENSSL_THREADS_POSIX)
+# define _GNU_SOURCE
+# include <errno.h>
+# include <sys/types.h>
+# include <unistd.h>
+
+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 (file)
index 0000000..b71cda8
--- /dev/null
@@ -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 <internal/thread_arch.h>
+
+#if defined(OPENSSL_THREADS_WINNT)
+# include <process.h>
+# include <windows.h>
+
+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 (file)
index 0000000..3ab689d
--- /dev/null
@@ -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 (file)
index 0000000..22af876
--- /dev/null
@@ -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 <openssl/configuration.h>
+#include <openssl/e_os2.h>
+#include <openssl/types.h>
+#include <openssl/crypto.h>
+#include <internal/thread.h>
+#include <internal/thread_arch.h>
+
+#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
index a51679b97edde47c264b37f4b97719f9b84e7d7e..fd2d6a207f1dfb9df4f08aaf94b8e9abf80722c0 100644 (file)
@@ -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<lock> 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
index 143f6d6b6d6b67115865a0599fda16d2238384e5..950d6f11e43ef1b3533db76efdffab7cc47ffd1e 100644 (file)
@@ -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
index 71b6b125f3803d2f4a69d833f7146e72e38dd953..700f387531f9f3a09823c0f2d3711eb56335218c 100644 (file)
@@ -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 (file)
index 0000000..8c5bad7
--- /dev/null
@@ -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 <openssl/configuration.h>
+# include <internal/thread_arch.h>
+# include <openssl/e_os2.h>
+# include <openssl/types.h>
+# include <internal/cryptlib.h>
+# 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 (file)
index 0000000..fcf312f
--- /dev/null
@@ -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 <openssl/configuration.h>
+# include <openssl/e_os2.h>
+
+# if defined(_WIN32)
+#  include <windows.h>
+# 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 <openssl/crypto.h>
+
+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 (file)
index 0000000..68ecf9c
--- /dev/null
@@ -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 <openssl/types.h>
+
+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 */
index 6caaa618cf089b19706d654de0a73cc340455778..e2cfddb22264d93a5bd9715feaed6ec9256d58d6 100644 (file)
@@ -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
index 505b2cf07e76b7477370952deb33a543f1c4c784..af1d78a3b7e2e124070fd7f7f917944f3dbfe4ab 100644 (file)
 #endif
 
 #include <string.h>
+#include <internal/cryptlib.h>
+#include <internal/thread_arch.h>
+#include <internal/thread.h>
 #include <openssl/crypto.h>
 #include <openssl/rsa.h>
 #include <openssl/aes.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
+#include <openssl/thread.h>
 #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;
 }
 
index ba446222108eed6795fdc8a3b03e5b922faae737..f5951d59e58008dc05e86eb9f08af873ac21f715 100644 (file)
@@ -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: