[openssl] master update

Matt Caswell matt at openssl.org
Thu Jan 14 17:30:57 UTC 2021


The branch master has been updated
       via  b11ba50fd9bd3c33e1627ca5c64f08b403e88173 (commit)
       via  7dd2cb569358591bb832af66fdabd6a6c580c1d4 (commit)
       via  b457c8f514130d3b92de574620d38c1058eb7b35 (commit)
       via  f5a50c2a07e288187c14b784be253b3a2a23483b (commit)
       via  2c40421440d260ddb97a807b064033f61ae3b2b3 (commit)
       via  c25a1524aad3a2f3a5d74880d8016de31f59adc8 (commit)
       via  886ad0045bf128795049b48f7d7977f72cc7220c (commit)
       via  ae95a40e8d453aa9d4f6499568f658ffc88a7d6e (commit)
       via  f6b72c7d75658e843ea0864e2f202cdc091020f9 (commit)
      from  c476c06f507a2c64a59c8cc86f2109aa00cf5133 (commit)


- Log -----------------------------------------------------------------
commit b11ba50fd9bd3c33e1627ca5c64f08b403e88173
Author: Matt Caswell <matt at openssl.org>
Date:   Tue Jan 12 16:50:17 2021 +0000

    Fix a failure where fetches can return NULL in multi-threaded code
    
    When a fetch is attempted simultaneously from multiple threads then both
    threads can attempt to construct the method. However only one of those
    will get added to the global evp method store. The one that "lost" the
    race to add the method to the global evp method store ended up with the
    fetch call returning NULL, instead of returning the method that was
    already available.
    
    Fixes #13682
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit 7dd2cb569358591bb832af66fdabd6a6c580c1d4
Author: Matt Caswell <matt at openssl.org>
Date:   Mon Jan 11 17:02:01 2021 +0000

    Fix an issue in provider_activate_fallbacks()
    
    The above function was running while holding the store lock with a read
    lock. Unfortunately it actually modifies the store, so a write lock is
    required instead.
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit b457c8f514130d3b92de574620d38c1058eb7b35
Author: Matt Caswell <matt at openssl.org>
Date:   Mon Jan 11 17:01:07 2021 +0000

    Extend the threads test to add simple fetch from multi threads
    
    Issue #13682 suggests that doing a simple fetch from multi-threads may
    result in issues so we add a test for that.
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit f5a50c2a07e288187c14b784be253b3a2a23483b
Author: Matt Caswell <matt at openssl.org>
Date:   Fri Jan 8 13:48:13 2021 +0000

    Enable locking on the primary DRBG when we create it
    
    The primary DRBG may be shared across multiple threads and therefore
    we must use locking to access it. Previously we were enabling that locking
    lazily when we attempted to obtain one of the child DRBGs. Part of the
    process of enabling the lock, is to create the lock. But if we create the
    lock lazily then it is too late - we may race with other threads where each
    thread is independently attempting to enable the locking. This results
    in multiple locks being created - only one of which "sticks" and the rest
    are leaked.
    
    Instead we enable locking on the primary when we first create it. This is
    already locked and therefore we cannot race.
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit 2c40421440d260ddb97a807b064033f61ae3b2b3
Author: Matt Caswell <matt at openssl.org>
Date:   Fri Jan 8 13:22:59 2021 +0000

    Make sure we take the ctx->lock in ossl_lib_ctx_generic_new()
    
    The function ossl_lib_ctx_generic_new() modifies the exdata. This may
    be simultaneously being modified by other threads and therefore we need
    to make sure we take the lock before doing so.
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit c25a1524aad3a2f3a5d74880d8016de31f59adc8
Author: Matt Caswell <matt at openssl.org>
Date:   Fri Dec 11 16:29:25 2020 +0000

    Lock the provider operation_bits
    
    The provider operation_bits array can see concurrent access by multiple
    threads and can be reallocated at any time. Therefore we need to ensure
    that it is appropriately locked.
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit 886ad0045bf128795049b48f7d7977f72cc7220c
Author: Matt Caswell <matt at openssl.org>
Date:   Thu Dec 10 16:57:33 2020 +0000

    Document the core_thread_start upcall
    
    The core_thread_start upcall previously had a placeholder in the docs.
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit ae95a40e8d453aa9d4f6499568f658ffc88a7d6e
Author: Matt Caswell <matt at openssl.org>
Date:   Thu Dec 10 15:39:58 2020 +0000

    Add a test for performing work in multiple concurrent threads
    
    We test both the default provider and the fips provider
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

commit f6b72c7d75658e843ea0864e2f202cdc091020f9
Author: Matt Caswell <matt at openssl.org>
Date:   Thu Dec 10 14:44:25 2020 +0000

    Fix a crash with multi-threaded applications using the FIPS module
    
    The FIPS implementation of the ossl_ctx_thread_stop function needs to
    use an OSSL_LIB_CTX - but gets passed a provctx as an argument. It was
    assuming that these are the same thing (which was true at one point
    during development) - but that is no longer the case. The fix is to
    get the OSSL_LIB_CTX out of the provctx.
    
    Fixes #13469
    
    Reviewed-by: Tomas Mraz <tmraz at fedoraproject.org>
    (Merged from https://github.com/openssl/openssl/pull/13660)

-----------------------------------------------------------------------

Summary of changes:
 crypto/context.c               |   8 +-
 crypto/core_fetch.c            |  10 +++
 crypto/err/openssl.txt         |   2 +-
 crypto/evp/evp_err.c           |   2 +-
 crypto/evp/evp_rand.c          |   6 --
 crypto/initthread.c            |   6 +-
 crypto/provider_core.c         |  73 ++++++++++------
 crypto/rand/rand_lib.c         |  11 +++
 doc/man3/EVP_RAND.pod          |   4 +-
 doc/man7/provider-base.pod     |  11 ++-
 include/openssl/evperr.h       |   2 +-
 test/recipes/90-test_threads.t |  32 ++++++-
 test/threadstest.c             | 183 ++++++++++++++++++++++++++++++++++++++++-
 13 files changed, 307 insertions(+), 43 deletions(-)

diff --git a/crypto/context.c b/crypto/context.c
index c351ff9619..953de5a489 100644
--- a/crypto/context.c
+++ b/crypto/context.c
@@ -228,10 +228,14 @@ static void ossl_lib_ctx_generic_new(void *parent_ign, void *ptr_ign,
                                      long argl_ign, void *argp)
 {
     const OSSL_LIB_CTX_METHOD *meth = argp;
-    void *ptr = meth->new_func(crypto_ex_data_get_ossl_lib_ctx(ad));
+    OSSL_LIB_CTX *ctx = crypto_ex_data_get_ossl_lib_ctx(ad);
+    void *ptr = meth->new_func(ctx);
 
-    if (ptr != NULL)
+    if (ptr != NULL) {
+        CRYPTO_THREAD_write_lock(ctx->lock);
         CRYPTO_set_ex_data(ad, index, ptr);
+        CRYPTO_THREAD_unlock(ctx->lock);
+    }
 }
 static void ossl_lib_ctx_generic_free(void *parent_ign, void *ptr,
                                       CRYPTO_EX_DATA *ad, int index,
diff --git a/crypto/core_fetch.c b/crypto/core_fetch.c
index 4fb432754b..12d52a03d0 100644
--- a/crypto/core_fetch.c
+++ b/crypto/core_fetch.c
@@ -128,6 +128,16 @@ void *ossl_method_construct(OSSL_LIB_CTX *libctx, int operation_id,
                               &cbdata);
 
         method = mcm->get(libctx, cbdata.store, mcm_data);
+        if (method == NULL) {
+            /*
+             * If we get here then we did not construct the method that we
+             * attempted to construct. It's possible that another thread got
+             * there first and so we skipped construction (pre-condition
+             * failed). We check the global store again to see if it has
+             * appeared by now.
+             */
+            method = mcm->get(libctx, NULL, mcm_data);
+        }
         mcm->dealloc_tmp_store(cbdata.store);
     }
 
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index bb200a7960..40f93ba0cd 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -2609,7 +2609,7 @@ EVP_R_PRIVATE_KEY_ENCODE_ERROR:146:private key encode error
 EVP_R_PUBLIC_KEY_NOT_RSA:106:public key not rsa
 EVP_R_SET_DEFAULT_PROPERTY_FAILURE:209:set default property failure
 EVP_R_TOO_MANY_RECORDS:183:too many records
-EVP_R_UNABLE_TO_ENABLE_PARENT_LOCKING:212:unable to enable parent locking
+EVP_R_UNABLE_TO_ENABLE_LOCKING:212:unable to enable locking
 EVP_R_UNABLE_TO_GET_MAXIMUM_REQUEST_SIZE:215:unable to get maximum request size
 EVP_R_UNABLE_TO_GET_RANDOM_STRENGTH:216:unable to get random strength
 EVP_R_UNABLE_TO_LOCK_CONTEXT:211:unable to lock context
diff --git a/crypto/evp/evp_err.c b/crypto/evp/evp_err.c
index e08c373b33..ab6e3e879d 100644
--- a/crypto/evp/evp_err.c
+++ b/crypto/evp/evp_err.c
@@ -152,7 +152,7 @@ static const ERR_STRING_DATA EVP_str_reasons[] = {
     {ERR_PACK(ERR_LIB_EVP, 0, EVP_R_SET_DEFAULT_PROPERTY_FAILURE),
     "set default property failure"},
     {ERR_PACK(ERR_LIB_EVP, 0, EVP_R_TOO_MANY_RECORDS), "too many records"},
-    {ERR_PACK(ERR_LIB_EVP, 0, EVP_R_UNABLE_TO_ENABLE_PARENT_LOCKING),
+    {ERR_PACK(ERR_LIB_EVP, 0, EVP_R_UNABLE_TO_ENABLE_LOCKING),
     "unable to enable parent locking"},
     {ERR_PACK(ERR_LIB_EVP, 0, EVP_R_UNABLE_TO_GET_MAXIMUM_REQUEST_SIZE),
     "unable to get maximum request size"},
diff --git a/crypto/evp/evp_rand.c b/crypto/evp/evp_rand.c
index 6a4f57414c..42e51b4ea1 100644
--- a/crypto/evp/evp_rand.c
+++ b/crypto/evp/evp_rand.c
@@ -333,12 +333,6 @@ EVP_RAND_CTX *EVP_RAND_CTX_new(EVP_RAND *rand, EVP_RAND_CTX *parent)
         return NULL;
     }
     if (parent != NULL) {
-        if (!EVP_RAND_enable_locking(parent)) {
-            ERR_raise(ERR_LIB_EVP, EVP_R_UNABLE_TO_ENABLE_PARENT_LOCKING);
-            CRYPTO_THREAD_lock_free(ctx->refcnt_lock);
-            OPENSSL_free(ctx);
-            return NULL;
-        }
         if (!evp_rand_ctx_up_ref(parent)) {
             ERR_raise(ERR_LIB_EVP, ERR_R_INTERNAL_ERROR);
             CRYPTO_THREAD_lock_free(ctx->refcnt_lock);
diff --git a/crypto/initthread.c b/crypto/initthread.c
index 8a7afbfd86..f172c53cd6 100644
--- a/crypto/initthread.c
+++ b/crypto/initthread.c
@@ -14,6 +14,8 @@
 #include "internal/thread_once.h"
 
 #ifdef FIPS_MODULE
+#include "prov/provider_ctx.h"
+
 /*
  * Thread aware code may want to be told about thread stop events. We register
  * to hear about those thread stop events when we see a new thread has started.
@@ -281,7 +283,7 @@ static const OSSL_LIB_CTX_METHOD thread_event_ossl_ctx_method = {
 void ossl_ctx_thread_stop(void *arg)
 {
     THREAD_EVENT_HANDLER **hands;
-    OSSL_LIB_CTX *ctx = arg;
+    OSSL_LIB_CTX *ctx = PROV_LIBCTX_OF(arg);
     CRYPTO_THREAD_LOCAL *local
         = ossl_lib_ctx_get_data(ctx, OSSL_LIB_CTX_THREAD_EVENT_HANDLER_INDEX,
                                 &thread_event_ossl_ctx_method);
@@ -289,7 +291,7 @@ void ossl_ctx_thread_stop(void *arg)
     if (local == NULL)
         return;
     hands = init_get_thread_local(local, 0, 0);
-    init_thread_stop(arg, hands);
+    init_thread_stop(ctx, hands);
     OPENSSL_free(hands);
 }
 #endif /* FIPS_MODULE */
diff --git a/crypto/provider_core.c b/crypto/provider_core.c
index f0d6fb20f8..91cfcaa9ec 100644
--- a/crypto/provider_core.c
+++ b/crypto/provider_core.c
@@ -85,6 +85,7 @@ struct ossl_provider_st {
      */
     unsigned char *operation_bits;
     size_t operation_bits_sz;
+    CRYPTO_RWLOCK *opbits_lock;
 
     /* Provider side data */
     void *provctx;
@@ -252,6 +253,7 @@ static OSSL_PROVIDER *provider_new(const char *name,
         || (prov->activatecnt_lock = CRYPTO_THREAD_lock_new()) == NULL
 #endif
         || !ossl_provider_up_ref(prov) /* +1 One reference to be returned */
+        || (prov->opbits_lock = CRYPTO_THREAD_lock_new()) == NULL
         || (prov->name = OPENSSL_strdup(name)) == NULL) {
         ossl_provider_free(prov);
         ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE);
@@ -371,6 +373,7 @@ void ossl_provider_free(OSSL_PROVIDER *prov)
             OPENSSL_free(prov->name);
             OPENSSL_free(prov->path);
             sk_INFOPAIR_pop_free(prov->parameters, free_infopair);
+            CRYPTO_THREAD_lock_free(prov->opbits_lock);
 #ifndef HAVE_ATOMICS
             CRYPTO_THREAD_lock_free(prov->refcnt_lock);
             CRYPTO_THREAD_lock_free(prov->activatecnt_lock);
@@ -726,34 +729,50 @@ static int provider_forall_loaded(struct provider_store_st *store,
  */
 static void provider_activate_fallbacks(struct provider_store_st *store)
 {
-    if (store->use_fallbacks) {
-        int num_provs = sk_OSSL_PROVIDER_num(store->providers);
-        int activated_fallback_count = 0;
-        int i;
+    int use_fallbacks;
+    int num_provs;
+    int activated_fallback_count = 0;
+    int i;
+
+    CRYPTO_THREAD_read_lock(store->lock);
+    use_fallbacks = store->use_fallbacks;
+    CRYPTO_THREAD_unlock(store->lock);
+    if (!use_fallbacks)
+        return;
+
+    CRYPTO_THREAD_write_lock(store->lock);
+    /* Check again, just in case another thread changed it */
+    use_fallbacks = store->use_fallbacks;
+    if (!use_fallbacks) {
+        CRYPTO_THREAD_unlock(store->lock);
+        return;
+    }
 
-        for (i = 0; i < num_provs; i++) {
-            OSSL_PROVIDER *prov = sk_OSSL_PROVIDER_value(store->providers, i);
+    num_provs = sk_OSSL_PROVIDER_num(store->providers);
+    for (i = 0; i < num_provs; i++) {
+        OSSL_PROVIDER *prov = sk_OSSL_PROVIDER_value(store->providers, i);
 
-            if (ossl_provider_up_ref(prov)) {
-                if (prov->flag_fallback) {
-                    if (provider_activate(prov)) {
-                        prov->flag_activated_as_fallback = 1;
-                        activated_fallback_count++;
-                    }
+        if (ossl_provider_up_ref(prov)) {
+            if (prov->flag_fallback) {
+                if (provider_activate(prov)) {
+                    prov->flag_activated_as_fallback = 1;
+                    activated_fallback_count++;
                 }
-                ossl_provider_free(prov);
             }
+            ossl_provider_free(prov);
         }
-
-        /*
-         * We assume that all fallbacks have been added to the store before
-         * any fallback is activated.
-         * TODO: We may have to reconsider this, IF we find ourselves adding
-         * fallbacks after any previous fallback has been activated.
-         */
-        if (activated_fallback_count > 0)
-            store->use_fallbacks = 0;
     }
+
+    /*
+     * We assume that all fallbacks have been added to the store before
+     * any fallback is activated.
+     * TODO: We may have to reconsider this, IF we find ourselves adding
+     * fallbacks after any previous fallback has been activated.
+     */
+    if (activated_fallback_count > 0)
+        store->use_fallbacks = 0;
+
+    CRYPTO_THREAD_unlock(store->lock);
 }
 
 int ossl_provider_forall_loaded(OSSL_LIB_CTX *ctx,
@@ -773,10 +792,9 @@ int ossl_provider_forall_loaded(OSSL_LIB_CTX *ctx,
 #endif
 
     if (store != NULL) {
-        CRYPTO_THREAD_read_lock(store->lock);
-
         provider_activate_fallbacks(store);
 
+        CRYPTO_THREAD_read_lock(store->lock);
         /*
          * Now, we sweep through all providers
          */
@@ -791,9 +809,7 @@ int ossl_provider_forall_loaded(OSSL_LIB_CTX *ctx,
 int ossl_provider_available(OSSL_PROVIDER *prov)
 {
     if (prov != NULL) {
-        CRYPTO_THREAD_read_lock(prov->store->lock);
         provider_activate_fallbacks(prov->store);
-        CRYPTO_THREAD_unlock(prov->store->lock);
 
         return prov->flag_activated;
     }
@@ -907,11 +923,13 @@ int ossl_provider_set_operation_bit(OSSL_PROVIDER *provider, size_t bitnum)
     size_t byte = bitnum / 8;
     unsigned char bit = (1 << (bitnum % 8)) & 0xFF;
 
+    CRYPTO_THREAD_write_lock(provider->opbits_lock);
     if (provider->operation_bits_sz <= byte) {
         unsigned char *tmp = OPENSSL_realloc(provider->operation_bits,
                                              byte + 1);
 
         if (tmp == NULL) {
+            CRYPTO_THREAD_unlock(provider->opbits_lock);
             ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE);
             return 0;
         }
@@ -921,6 +939,7 @@ int ossl_provider_set_operation_bit(OSSL_PROVIDER *provider, size_t bitnum)
         provider->operation_bits_sz = byte + 1;
     }
     provider->operation_bits[byte] |= bit;
+    CRYPTO_THREAD_unlock(provider->opbits_lock);
     return 1;
 }
 
@@ -936,8 +955,10 @@ int ossl_provider_test_operation_bit(OSSL_PROVIDER *provider, size_t bitnum,
     }
 
     *result = 0;
+    CRYPTO_THREAD_read_lock(provider->opbits_lock);
     if (provider->operation_bits_sz > byte)
         *result = ((provider->operation_bits[byte] & bit) != 0);
+    CRYPTO_THREAD_unlock(provider->opbits_lock);
     return 1;
 }
 
diff --git a/crypto/rand/rand_lib.c b/crypto/rand/rand_lib.c
index f0284aab08..01927401ab 100644
--- a/crypto/rand/rand_lib.c
+++ b/crypto/rand/rand_lib.c
@@ -571,6 +571,17 @@ EVP_RAND_CTX *RAND_get0_primary(OSSL_LIB_CTX *ctx)
             dgbl->primary = rand_new_drbg(ctx, dgbl->seed,
                                           PRIMARY_RESEED_INTERVAL,
                                           PRIMARY_RESEED_TIME_INTERVAL);
+        /*
+         * The primary DRBG may be shared between multiple threads so we must
+         * enable locking.
+         */
+        if (dgbl->primary != NULL && !EVP_RAND_enable_locking(dgbl->primary)) {
+            ERR_raise(ERR_LIB_EVP, EVP_R_UNABLE_TO_ENABLE_LOCKING);
+            EVP_RAND_CTX_free(dgbl->primary);
+            dgbl->primary = NULL;
+            CRYPTO_THREAD_lock_free(dgbl->lock);
+            return NULL;
+        }
         CRYPTO_THREAD_unlock(dgbl->lock);
     }
     return dgbl->primary;
diff --git a/doc/man3/EVP_RAND.pod b/doc/man3/EVP_RAND.pod
index e53cddff2f..4e1eb88afd 100644
--- a/doc/man3/EVP_RAND.pod
+++ b/doc/man3/EVP_RAND.pod
@@ -152,7 +152,9 @@ appropriate size.
 
 EVP_RAND_enable_locking() enables locking for the RAND I<ctx> and all of
 its parents.  After this I<ctx> will operate in a thread safe manner, albeit
-more slowly.
+more slowly. This function is not itself thread safe if called with the same
+I<ctx> from multiple threads. Typically locking should be enabled before a
+I<ctx> is shared across multiple threads.
 
 EVP_RAND_get_params() retrieves details about the implementation
 I<rand>.
diff --git a/doc/man7/provider-base.pod b/doc/man7/provider-base.pod
index 7e8f5188a5..e2315e7bc4 100644
--- a/doc/man7/provider-base.pod
+++ b/doc/man7/provider-base.pod
@@ -18,8 +18,11 @@ provider-base
  /* Functions offered by libcrypto to the providers */
  const OSSL_ITEM *core_gettable_params(const OSSL_CORE_HANDLE *handle);
  int core_get_params(const OSSL_CORE_HANDLE *handle, OSSL_PARAM params[]);
+
+ typedef void (*OSSL_thread_stop_handler_fn)(void *arg);
  int core_thread_start(const OSSL_CORE_HANDLE *handle,
                        OSSL_thread_stop_handler_fn handfn);
+
  OPENSSL_CORE_CTX *core_get_libctx(const OSSL_CORE_HANDLE *handle);
  void core_new_error(const OSSL_CORE_HANDLE *handle);
  void core_set_error_debug(const OSSL_CORE_HANDLE *handle,
@@ -164,7 +167,13 @@ core_get_params() retrieves parameters from the core for the given I<handle>.
 See L</Core parameters> below for a description of currently known
 parameters.
 
-=for comment core_thread_start() TBA
+The core_thread_start() function informs the core that the provider has started
+an interest in the current thread. The core will inform the provider when the
+thread eventually stops. It must be passed the I<handle> for this provider, as
+well as a callback I<handfn> which will be called when the thread stops. The
+callback will subsequently be called from the thread that is stopping and gets
+passed the provider context as an argument. This may be useful to perform thread
+specific clean up such as freeing thread local variables.
 
 core_get_libctx() retrieves the library context in which the library
 object for the current provider is stored, accessible through the I<handle>.
diff --git a/include/openssl/evperr.h b/include/openssl/evperr.h
index c25cc49025..309f4cd341 100644
--- a/include/openssl/evperr.h
+++ b/include/openssl/evperr.h
@@ -243,7 +243,7 @@
 # define EVP_R_PUBLIC_KEY_NOT_RSA                         106
 # define EVP_R_SET_DEFAULT_PROPERTY_FAILURE               209
 # define EVP_R_TOO_MANY_RECORDS                           183
-# define EVP_R_UNABLE_TO_ENABLE_PARENT_LOCKING            212
+# define EVP_R_UNABLE_TO_ENABLE_LOCKING                   212
 # define EVP_R_UNABLE_TO_GET_MAXIMUM_REQUEST_SIZE         215
 # define EVP_R_UNABLE_TO_GET_RANDOM_STRENGTH              216
 # define EVP_R_UNABLE_TO_LOCK_CONTEXT                     211
diff --git a/test/recipes/90-test_threads.t b/test/recipes/90-test_threads.t
index e629f24d1c..fa4d2b8de9 100644
--- a/test/recipes/90-test_threads.t
+++ b/test/recipes/90-test_threads.t
@@ -8,5 +8,35 @@
 
 
 use OpenSSL::Test::Simple;
+use OpenSSL::Test qw/:DEFAULT srctop_file srctop_dir bldtop_dir bldtop_file/;
+use OpenSSL::Test::Utils;
+use Cwd qw(abs_path);
 
-simple_test("test_threads", "threadstest");
+BEGIN {
+setup("test_threads");
+}
+
+use lib srctop_dir('Configurations');
+use lib bldtop_dir('.');
+use platform;
+
+my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0);
+
+
+plan tests => 1 + ($no_fips ? 0 : 1);
+
+if (!$no_fips) {
+    my $infile = bldtop_file('providers', platform->dso('fips'));
+    ok(run(app(['openssl', 'fipsinstall',
+            '-out', bldtop_file('providers', 'fipsmodule.cnf'),
+            '-module', $infile])),
+    "fipsinstall");
+}
+
+if ($no_fips) {
+    $ENV{OPENSSL_CONF} = abs_path(srctop_file("test", "default.cnf"));
+    ok(run(test(["threadstest"])), "running test_threads");
+} else {
+    $ENV{OPENSSL_CONF} = abs_path(srctop_file("test", "default-and-fips.cnf"));
+    ok(run(test(["threadstest", "-fips"])), "running test_threads");
+}
diff --git a/test/threadstest.c b/test/threadstest.c
index d7ed59781d..13405f4948 100644
--- a/test/threadstest.c
+++ b/test/threadstest.c
@@ -11,9 +11,15 @@
 # include <windows.h>
 #endif
 
+#include <string.h>
 #include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/aes.h>
+#include <openssl/rsa.h>
 #include "testutil.h"
 
+static int do_fips = 0;
+
 #if !defined(OPENSSL_THREADS) || defined(CRYPTO_TDEBUG)
 
 typedef unsigned int thread_t;
@@ -254,16 +260,191 @@ static int test_atomic(void)
 
     testresult = 1;
  err:
-
     CRYPTO_THREAD_lock_free(lock);
     return testresult;
 }
 
+static OSSL_LIB_CTX *multi_libctx = NULL;
+static int multi_success;
+
+static void thread_general_worker(void)
+{
+    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+    EVP_MD *md = EVP_MD_fetch(multi_libctx, "SHA2-256", NULL);
+    EVP_CIPHER_CTX *cipherctx = EVP_CIPHER_CTX_new();
+    EVP_CIPHER *ciph = EVP_CIPHER_fetch(multi_libctx, "AES-128-CBC", NULL);
+    const char *message = "Hello World";
+    size_t messlen = strlen(message);
+    /* Should be big enough for encryption output too */
+    unsigned char out[EVP_MAX_MD_SIZE];
+    const unsigned char key[AES_BLOCK_SIZE] = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+        0x0c, 0x0d, 0x0e, 0x0f
+    };
+    const unsigned char iv[AES_BLOCK_SIZE] = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+        0x0c, 0x0d, 0x0e, 0x0f
+    };
+    unsigned int mdoutl;
+    int ciphoutl;
+    EVP_PKEY_CTX *pctx = NULL;
+    EVP_PKEY *pkey = NULL;
+    int testresult = 0;
+    int i, isfips;
+
+    isfips = OSSL_PROVIDER_available(multi_libctx, "fips");
+
+    if (!TEST_ptr(mdctx)
+            || !TEST_ptr(md)
+            || !TEST_ptr(cipherctx)
+            || !TEST_ptr(ciph))
+        goto err;
+
+    /* Do some work */
+    for (i = 0; i < 5; i++) {
+        if (!TEST_true(EVP_DigestInit_ex(mdctx, md, NULL))
+                || !TEST_true(EVP_DigestUpdate(mdctx, message, messlen))
+                || !TEST_true(EVP_DigestFinal(mdctx, out, &mdoutl)))
+            goto err;
+    }
+    for (i = 0; i < 5; i++) {
+        if (!TEST_true(EVP_EncryptInit_ex(cipherctx, ciph, NULL, key, iv))
+                || !TEST_true(EVP_EncryptUpdate(cipherctx, out, &ciphoutl,
+                                                (unsigned char *)message,
+                                                messlen))
+                || !TEST_true(EVP_EncryptFinal(cipherctx, out, &ciphoutl)))
+            goto err;
+    }
+
+    pctx = EVP_PKEY_CTX_new_from_name(multi_libctx, "RSA", NULL);
+    if (!TEST_ptr(pctx)
+            || !TEST_int_gt(EVP_PKEY_keygen_init(pctx), 0)
+               /*
+                * We want the test to run quickly - not securely. Therefore we
+                * use an insecure bit length where we can (512). In the FIPS
+                * module though we must use a longer length.
+                */
+            || !TEST_int_gt(EVP_PKEY_CTX_set_rsa_keygen_bits(pctx,
+                                                             isfips ? 2048 : 512),
+                                                             0)
+            || !TEST_int_gt(EVP_PKEY_keygen(pctx, &pkey), 0))
+        goto err;
+
+    testresult = 1;
+ err:
+    EVP_MD_CTX_free(mdctx);
+    EVP_MD_free(md);
+    EVP_CIPHER_CTX_free(cipherctx);
+    EVP_CIPHER_free(ciph);
+    EVP_PKEY_CTX_free(pctx);
+    EVP_PKEY_free(pkey);
+    if (!testresult)
+        multi_success = 0;
+}
+
+static void thread_multi_simple_fetch(void)
+{
+    EVP_MD *md = EVP_MD_fetch(NULL, "SHA2-256", NULL);
+
+    if (md != NULL)
+        EVP_MD_free(md);
+    else
+        multi_success = 0;
+}
+
+/*
+ * Do work in multiple worker threads at the same time.
+ * Test 0: General worker, using the default provider
+ * Test 1: General worker, using the fips provider
+ * Test 2: Simple fetch worker
+ */
+static int test_multi(int idx)
+{
+    thread_t thread1, thread2;
+    int testresult = 0;
+    OSSL_PROVIDER *prov = NULL;
+    void (*worker)(void);
+
+    if (idx == 1 && !do_fips)
+        return TEST_skip("FIPS not supported");
+
+    multi_success = 1;
+    multi_libctx = OSSL_LIB_CTX_new();
+    if (!TEST_ptr(multi_libctx))
+        goto err;
+    prov = OSSL_PROVIDER_load(multi_libctx, (idx == 1) ? "fips" : "default");
+    if (!TEST_ptr(prov))
+        goto err;
+
+    switch (idx) {
+    case 0:
+    case 1:
+        worker = thread_general_worker;
+        break;
+    case 2:
+        worker = thread_multi_simple_fetch;
+        break;
+    default:
+        TEST_error("Invalid test index");
+        goto err;
+    }
+
+    if (!TEST_true(run_thread(&thread1, worker))
+            || !TEST_true(run_thread(&thread2, worker)))
+        goto err;
+
+    worker();
+
+    if (!TEST_true(wait_for_thread(thread1))
+            || !TEST_true(wait_for_thread(thread2))
+            || !TEST_true(multi_success))
+        goto err;
+
+    testresult = 1;
+
+ err:
+    OSSL_PROVIDER_unload(prov);
+    OSSL_LIB_CTX_free(multi_libctx);
+    return testresult;
+}
+
+typedef enum OPTION_choice {
+    OPT_ERR = -1,
+    OPT_EOF = 0,
+    OPT_FIPS,
+    OPT_TEST_ENUM
+} OPTION_CHOICE;
+
+const OPTIONS *test_get_options(void)
+{
+    static const OPTIONS options[] = {
+        OPT_TEST_OPTIONS_DEFAULT_USAGE,
+        { "fips", OPT_FIPS, '-', "Test the FIPS provider" },
+        { NULL }
+    };
+    return options;
+}
+
 int setup_tests(void)
 {
+    OPTION_CHOICE o;
+
+    while ((o = opt_next()) != OPT_EOF) {
+        switch (o) {
+        case OPT_FIPS:
+            do_fips = 1;
+            break;
+        case OPT_TEST_CASES:
+            break;
+        default:
+            return 0;
+        }
+    }
+
     ADD_TEST(test_lock);
     ADD_TEST(test_once);
     ADD_TEST(test_thread_local);
     ADD_TEST(test_atomic);
+    ADD_ALL_TESTS(test_multi, 3);
     return 1;
 }


More information about the openssl-commits mailing list