[openssl] master update

Matt Caswell matt at openssl.org
Mon Jun 17 15:25:27 UTC 2019


The branch master has been updated
       via  2be8c56a39b0ec2ec5af6ceaf729df154d784a43 (commit)
       via  ff6da65e0d81bae4af3fc3337a95b03595cf5878 (commit)
       via  52b18ce10498dc2d8ced7db31ead116f4eeca134 (commit)
       via  da747958c5db57dbe22c015d058be9db8a90f8f9 (commit)
       via  e41faf5784382a5d2bc23abebcf81b9f4708f6ec (commit)
       via  72592b866492b84ba5ca1251d1a45875764c7b27 (commit)
       via  242f84d06aca7030b2bd52043c39b0cb80c4fec6 (commit)
       via  d4c051cef338eecf092affbb479d1f87c1ea31d9 (commit)
      from  30478c97837a026ba56718f98d490adf7bce2760 (commit)


- Log -----------------------------------------------------------------
commit 2be8c56a39b0ec2ec5af6ceaf729df154d784a43
Author: Matt Caswell <matt at openssl.org>
Date:   Mon Jun 17 15:16:36 2019 +0100

    Standardise the function naming conventions in initthread.c
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

commit ff6da65e0d81bae4af3fc3337a95b03595cf5878
Author: Matt Caswell <matt at openssl.org>
Date:   Wed May 29 16:04:17 2019 +0100

    Document OPENSSL_thread_stop_ex()
    
    This new function works in the same way as OPENSSL_thread_stop() but
    for a specified OPENSSL_CTX.
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

commit 52b18ce10498dc2d8ced7db31ead116f4eeca134
Author: Matt Caswell <matt at openssl.org>
Date:   Tue May 28 16:21:19 2019 +0100

    Add the function OPENSSL_thread_stop_ex()
    
    This adds the ability to clean up a thread on a per OPENSSL_CTX basis.
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

commit da747958c5db57dbe22c015d058be9db8a90f8f9
Author: Matt Caswell <matt at openssl.org>
Date:   Mon May 27 16:31:27 2019 +0100

    Tell the FIPS provider about thread stop events
    
    The RAND code needs to know about threads stopping in order to cleanup
    local thread data. Therefore we add a callback for libcrypto to tell
    providers about such events.
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

commit e41faf5784382a5d2bc23abebcf81b9f4708f6ec
Author: Matt Caswell <matt at openssl.org>
Date:   Fri May 24 18:20:49 2019 +0100

    Provide a version of ossl_init_thread_start that works in FIPS mode
    
    This will need to be hooked up in a later commit with an event sent to
    the FIPS provider informing it of thread stop events.
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

commit 72592b866492b84ba5ca1251d1a45875764c7b27
Author: Matt Caswell <matt at openssl.org>
Date:   Fri May 24 17:52:17 2019 +0100

    Split thread intialisation and handling out of init.c
    
    We're going to need some of these functions in the FIPS module, but most
    of the rest of the code in init.c is not needed. Therefore we split it out.
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

commit 242f84d06aca7030b2bd52043c39b0cb80c4fec6
Author: Matt Caswell <matt at openssl.org>
Date:   Fri May 24 11:45:48 2019 +0100

    Convert thread stop handling into a publish/subscribe model
    
    In later commits this will allow providers to subscribe to thread stop
    events. We will need this in the FIPS module. We also make thread stop
    handling OPENSSL_CTX aware (different OPENSSL_CTXs may have different
    thread local data that needs cleaning up).
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

commit d4c051cef338eecf092affbb479d1f87c1ea31d9
Author: Matt Caswell <matt at openssl.org>
Date:   Tue May 28 15:58:08 2019 +0100

    Add the function openssl_ctx_get_concrete()
    
    This adds the ability to take an OPENSSL_CTX parameter and either return it
    as is (unchanged), or if it is NULL return a pointer to the default ctx.
    
    Reviewed-by: Richard Levitte <levitte at openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/9040)

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

Summary of changes:
 crypto/async/async.c                               |  11 +-
 crypto/build.info                                  |   4 +-
 crypto/context.c                                   |  53 ++---
 crypto/err/err.c                                   |   5 +-
 crypto/include/internal/async.h                    |   1 -
 crypto/include/internal/cryptlib_int.h             |  18 +-
 crypto/include/internal/err_int.h                  |   1 -
 crypto/include/internal/rand_int.h                 |   1 -
 crypto/init.c                                      | 128 +---------
 crypto/initthread.c                                | 259 +++++++++++++++++++++
 crypto/provider_core.c                             |   9 +-
 crypto/rand/drbg_lib.c                             |  17 +-
 doc/man3/OPENSSL_CTX.pod                           |   4 +
 doc/man3/OPENSSL_init_crypto.pod                   |  27 ++-
 include/internal/cryptlib.h                        |   6 +-
 include/openssl/core.h                             |  13 ++
 include/openssl/core_numbers.h                     |   9 +-
 include/openssl/crypto.h                           |   1 +
 providers/common/include/internal/providercommon.h |  10 +
 providers/fips/fipsprov.c                          |  63 ++++-
 test/evp_extra_test.c                              |   5 +
 util/libcrypto.num                                 |   1 +
 22 files changed, 450 insertions(+), 196 deletions(-)
 create mode 100644 crypto/initthread.c

diff --git a/crypto/async/async.c b/crypto/async/async.c
index 53d288c..bcb0030 100644
--- a/crypto/async/async.c
+++ b/crypto/async/async.c
@@ -30,11 +30,13 @@
 static CRYPTO_THREAD_LOCAL ctxkey;
 static CRYPTO_THREAD_LOCAL poolkey;
 
+static void async_delete_thread_state(void *arg);
+
 static async_ctx *async_ctx_new(void)
 {
     async_ctx *nctx;
 
-    if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_ASYNC))
+    if (!ossl_init_thread_start(NULL, async_delete_thread_state))
         return NULL;
 
     nctx = OPENSSL_malloc(sizeof(*nctx));
@@ -326,7 +328,7 @@ int ASYNC_init_thread(size_t max_size, size_t init_size)
     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
         return 0;
 
-    if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_ASYNC))
+    if (!ossl_init_thread_start(NULL, async_delete_thread_state))
         return 0;
 
     pool = OPENSSL_zalloc(sizeof(*pool));
@@ -374,7 +376,8 @@ err:
     return 0;
 }
 
-void async_delete_thread_state(void)
+/* TODO(3.0): arg ignored for now */
+static void async_delete_thread_state(void *arg)
 {
     async_pool *pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
 
@@ -393,7 +396,7 @@ void ASYNC_cleanup_thread(void)
     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
         return;
 
-    async_delete_thread_state();
+    async_delete_thread_state(NULL);
 }
 
 ASYNC_JOB *ASYNC_get_current_job(void)
diff --git a/crypto/build.info b/crypto/build.info
index cb8457a..e64a8de 100644
--- a/crypto/build.info
+++ b/crypto/build.info
@@ -67,8 +67,8 @@ SOURCE[../providers/fips]=$CORE_COMMON
 # Central utilities
 $UTIL_COMMON=\
         cryptlib.c mem.c mem_sec.c params.c bsearch.c ex_data.c o_str.c \
-        ctype.c threads_pthread.c threads_win.c threads_none.c context.c \
-        sparse_array.c $CPUIDASM
+        ctype.c threads_pthread.c threads_win.c threads_none.c initthread.c \
+        context.c sparse_array.c $CPUIDASM
 $UTIL_DEFINE=$CPUIDDEF
 
 SOURCE[../libcrypto]=$UTIL_COMMON \
diff --git a/crypto/context.c b/crypto/context.c
index 7a976c0..cc1ec0e 100644
--- a/crypto/context.c
+++ b/crypto/context.c
@@ -7,7 +7,7 @@
  * https://www.openssl.org/source/license.html
  */
 
-#include "internal/cryptlib.h"
+#include "internal/cryptlib_int.h"
 #include "internal/thread_once.h"
 
 struct openssl_ctx_onfree_list_st {
@@ -80,6 +80,8 @@ static int context_deinit(OPENSSL_CTX *ctx)
     if (ctx == NULL)
         return 1;
 
+    ossl_ctx_thread_stop(ctx);
+
     onfree = ctx->onfreelist;
     while (onfree != NULL) {
         onfree->fn(ctx);
@@ -129,6 +131,18 @@ void OPENSSL_CTX_free(OPENSSL_CTX *ctx)
     OPENSSL_free(ctx);
 }
 
+OPENSSL_CTX *openssl_ctx_get_concrete(OPENSSL_CTX *ctx)
+{
+#ifndef FIPS_MODE
+    if (ctx == NULL) {
+        if (!RUN_ONCE(&default_context_init, do_default_context_init))
+            return 0;
+        return default_context;
+    }
+#endif
+    return ctx;
+}
+
 static void openssl_ctx_generic_new(void *parent_ign, void *ptr_ign,
                                     CRYPTO_EX_DATA *ad, int index,
                                     long argl_ign, void *argp)
@@ -154,13 +168,7 @@ static int openssl_ctx_init_index(OPENSSL_CTX *ctx, int static_index,
 {
     int idx;
 
-#ifndef FIPS_MODE
-    if (ctx == NULL) {
-        if (!RUN_ONCE(&default_context_init, do_default_context_init))
-            return 0;
-        ctx = default_context;
-    }
-#endif
+    ctx = openssl_ctx_get_concrete(ctx);
     if (ctx == NULL)
         return 0;
 
@@ -180,13 +188,7 @@ void *openssl_ctx_get_data(OPENSSL_CTX *ctx, int index,
 {
     void *data = NULL;
 
-#ifndef FIPS_MODE
-    if (ctx == NULL) {
-        if (!RUN_ONCE(&default_context_init, do_default_context_init))
-            return NULL;
-        ctx = default_context;
-    }
-#endif
+    ctx = openssl_ctx_get_concrete(ctx);
     if (ctx == NULL)
         return NULL;
 
@@ -210,18 +212,7 @@ void *openssl_ctx_get_data(OPENSSL_CTX *ctx, int index,
 
 OSSL_EX_DATA_GLOBAL *openssl_ctx_get_ex_data_global(OPENSSL_CTX *ctx)
 {
-    /*
-     * The default context code is not needed in FIPS_MODE and ctx should never
-     * be NULL in the FIPS provider. However we compile this code out to ensure
-     * we fail immediately if ctx == NULL in FIPS_MODE
-     */
-#ifndef FIPS_MODE
-    if (ctx == NULL) {
-        if (!RUN_ONCE(&default_context_init, do_default_context_init))
-            return NULL;
-        ctx = default_context;
-    }
-#endif
+    ctx = openssl_ctx_get_concrete(ctx);
     if (ctx == NULL)
         return NULL;
     return &ctx->global;
@@ -232,13 +223,7 @@ int openssl_ctx_run_once(OPENSSL_CTX *ctx, unsigned int idx,
 {
     int done = 0, ret = 0;
 
-#ifndef FIPS_MODE
-    if (ctx == NULL) {
-        if (!RUN_ONCE(&default_context_init, do_default_context_init))
-            return 0;
-        ctx = default_context;
-    }
-#endif
+    ctx = openssl_ctx_get_concrete(ctx);
     if (ctx == NULL)
         return 0;
 
diff --git a/crypto/err/err.c b/crypto/err/err.c
index cf3ae4d..196f782 100644
--- a/crypto/err/err.c
+++ b/crypto/err/err.c
@@ -688,7 +688,8 @@ const char *ERR_reason_error_string(unsigned long e)
     return ((p == NULL) ? NULL : p->string);
 }
 
-void err_delete_thread_state(void)
+/* TODO(3.0): arg ignored for now */
+static void err_delete_thread_state(void *arg)
 {
     ERR_STATE *state = CRYPTO_THREAD_get_local(&err_thread_local);
     if (state == NULL)
@@ -740,7 +741,7 @@ ERR_STATE *ERR_get_state(void)
             return NULL;
         }
 
-        if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_ERR_STATE)
+        if (!ossl_init_thread_start(NULL, err_delete_thread_state)
                 || !CRYPTO_THREAD_set_local(&err_thread_local, state)) {
             ERR_STATE_free(state);
             CRYPTO_THREAD_set_local(&err_thread_local, NULL);
diff --git a/crypto/include/internal/async.h b/crypto/include/internal/async.h
index f5454f2..e9a89da 100644
--- a/crypto/include/internal/async.h
+++ b/crypto/include/internal/async.h
@@ -11,5 +11,4 @@
 
 int async_init(void);
 void async_deinit(void);
-void async_delete_thread_state(void);
 
diff --git a/crypto/include/internal/cryptlib_int.h b/crypto/include/internal/cryptlib_int.h
index 422ef01..a69bdcd 100644
--- a/crypto/include/internal/cryptlib_int.h
+++ b/crypto/include/internal/cryptlib_int.h
@@ -7,17 +7,16 @@
  * https://www.openssl.org/source/license.html
  */
 
+#include <openssl/core.h>
 #include "internal/cryptlib.h"
 
 /* This file is not scanned by mkdef.pl, whereas cryptlib.h is */
 
-struct thread_local_inits_st {
-    int async;
-    int err_state;
-    int rand;
-};
-
-int ossl_init_thread_start(uint64_t opts);
+int ossl_init_thread_start(void *arg,
+                           OSSL_thread_stop_handler_fn handfn);
+int ossl_init_thread(void);
+void ossl_cleanup_thread(void);
+void ossl_ctx_thread_stop(void *arg);
 
 /*
  * OPENSSL_INIT flags. The primary list of these is in crypto.h. Flags below
@@ -27,11 +26,6 @@ int ossl_init_thread_start(uint64_t opts);
 # define OPENSSL_INIT_ZLIB                   0x00010000L
 # define OPENSSL_INIT_BASE_ONLY              0x00040000L
 
-/* OPENSSL_INIT_THREAD flags */
-# define OPENSSL_INIT_THREAD_ASYNC           0x01
-# define OPENSSL_INIT_THREAD_ERR_STATE       0x02
-# define OPENSSL_INIT_THREAD_RAND            0x04
-
 int ossl_trace_init(void);
 void ossl_trace_cleanup(void);
 void ossl_malloc_setup_failures(void);
diff --git a/crypto/include/internal/err_int.h b/crypto/include/internal/err_int.h
index a2006ca..68c6d62 100644
--- a/crypto/include/internal/err_int.h
+++ b/crypto/include/internal/err_int.h
@@ -12,7 +12,6 @@
 
 int err_load_crypto_strings_int(void);
 void err_cleanup(void);
-void err_delete_thread_state(void);
 int err_shelve_state(void **);
 void err_unshelve_state(void *);
 
diff --git a/crypto/include/internal/rand_int.h b/crypto/include/internal/rand_int.h
index 53896ce..c1e5e03 100644
--- a/crypto/include/internal/rand_int.h
+++ b/crypto/include/internal/rand_int.h
@@ -24,7 +24,6 @@
 typedef struct rand_pool_st RAND_POOL;
 
 void rand_cleanup_int(void);
-void drbg_delete_thread_state(void);
 void rand_fork(void);
 
 /* Hardware-based seeding functions. */
diff --git a/crypto/init.c b/crypto/init.c
index e73c9ba..8755e21 100644
--- a/crypto/init.c
+++ b/crypto/init.c
@@ -31,51 +31,6 @@
 
 static int stopped = 0;
 
-/*
- * Since per-thread-specific-data destructors are not universally
- * available, i.e. not on Windows, only below CRYPTO_THREAD_LOCAL key
- * is assumed to have destructor associated. And then an effort is made
- * to call this single destructor on non-pthread platform[s].
- *
- * Initial value is "impossible". It is used as guard value to shortcut
- * destructor for threads terminating before libcrypto is initialized or
- * after it's de-initialized. Access to the key doesn't have to be
- * serialized for the said threads, because they didn't use libcrypto
- * and it doesn't matter if they pick "impossible" or derefernce real
- * key value and pull NULL past initialization in the first thread that
- * intends to use libcrypto.
- */
-static union {
-    long sane;
-    CRYPTO_THREAD_LOCAL value;
-} destructor_key = { -1 };
-
-static void ossl_init_thread_stop(struct thread_local_inits_st *locals);
-
-static void ossl_init_thread_destructor(void *local)
-{
-    ossl_init_thread_stop((struct thread_local_inits_st *)local);
-}
-
-static struct thread_local_inits_st *ossl_init_get_thread_local(int alloc)
-{
-    struct thread_local_inits_st *local =
-        CRYPTO_THREAD_get_local(&destructor_key.value);
-
-    if (alloc) {
-        if (local == NULL
-            && (local = OPENSSL_zalloc(sizeof(*local))) != NULL
-            && !CRYPTO_THREAD_set_local(&destructor_key.value, local)) {
-            OPENSSL_free(local);
-            return NULL;
-        }
-    } else {
-        CRYPTO_THREAD_set_local(&destructor_key.value, NULL);
-    }
-
-    return local;
-}
-
 typedef struct ossl_init_stop_st OPENSSL_INIT_STOP;
 struct ossl_init_stop_st {
     void (*handler)(void);
@@ -89,8 +44,6 @@ static CRYPTO_ONCE base = CRYPTO_ONCE_STATIC_INIT;
 static int base_inited = 0;
 DEFINE_RUN_ONCE_STATIC(ossl_init_base)
 {
-    CRYPTO_THREAD_LOCAL key;
-
     if (ossl_trace_init() == 0)
         return 0;
 
@@ -98,13 +51,14 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_base)
 #ifndef OPENSSL_NO_CRYPTO_MDEBUG
     ossl_malloc_setup_failures();
 #endif
-    if (!CRYPTO_THREAD_init_local(&key, ossl_init_thread_destructor))
-        return 0;
+
     if ((init_lock = CRYPTO_THREAD_lock_new()) == NULL)
         goto err;
     OPENSSL_cpuid_setup();
 
-    destructor_key.value = key;
+    if (!ossl_init_thread())
+        return 0;
+
     base_inited = 1;
     return 1;
 
@@ -113,7 +67,6 @@ err:
     CRYPTO_THREAD_lock_free(init_lock);
     init_lock = NULL;
 
-    CRYPTO_THREAD_cleanup_local(&key);
     return 0;
 }
 
@@ -417,76 +370,9 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_zlib)
 }
 #endif
 
-static void ossl_init_thread_stop(struct thread_local_inits_st *locals)
-{
-    /* Can't do much about this */
-    if (locals == NULL)
-        return;
-
-    if (locals->async) {
-        OSSL_TRACE(INIT, "async_delete_thread_state()\n");
-        async_delete_thread_state();
-    }
-
-    if (locals->err_state) {
-        OSSL_TRACE(INIT, "err_delete_thread_state()\n");
-        err_delete_thread_state();
-    }
-
-    if (locals->rand) {
-        OSSL_TRACE(INIT, "drbg_delete_thread_state()\n");
-        drbg_delete_thread_state();
-    }
-
-    OPENSSL_free(locals);
-}
-
-void OPENSSL_thread_stop(void)
-{
-    if (destructor_key.sane != -1)
-        ossl_init_thread_stop(ossl_init_get_thread_local(0));
-}
-
-int ossl_init_thread_start(uint64_t opts)
-{
-    struct thread_local_inits_st *locals;
-
-    if (!OPENSSL_init_crypto(0, NULL))
-        return 0;
-
-    locals = ossl_init_get_thread_local(1);
-
-    if (locals == NULL)
-        return 0;
-
-    if (opts & OPENSSL_INIT_THREAD_ASYNC) {
-        OSSL_TRACE(INIT,
-                   "ossl_init_thread_start: "
-                   "marking thread for async\n");
-        locals->async = 1;
-    }
-
-    if (opts & OPENSSL_INIT_THREAD_ERR_STATE) {
-        OSSL_TRACE(INIT,
-                   "ossl_init_thread_start: "
-                   "marking thread for err_state\n");
-        locals->err_state = 1;
-    }
-
-    if (opts & OPENSSL_INIT_THREAD_RAND) {
-        OSSL_TRACE(INIT,
-                   "ossl_init_thread_start: "
-                   "marking thread for rand\n");
-        locals->rand = 1;
-    }
-
-    return 1;
-}
-
 void OPENSSL_cleanup(void)
 {
     OPENSSL_INIT_STOP *currhandler, *lasthandler;
-    CRYPTO_THREAD_LOCAL key;
 
     /*
      * TODO(3.0): This function needs looking at with a view to moving most/all
@@ -506,7 +392,7 @@ void OPENSSL_cleanup(void)
      * Thread stop may not get automatically called by the thread library for
      * the very last thread in some situations, so call it directly.
      */
-    ossl_init_thread_stop(ossl_init_get_thread_local(0));
+    OPENSSL_thread_stop();
 
     currhandler = stop_handlers;
     while (currhandler != NULL) {
@@ -542,9 +428,7 @@ void OPENSSL_cleanup(void)
         err_free_strings_int();
     }
 
-    key = destructor_key.value;
-    destructor_key.sane = -1;
-    CRYPTO_THREAD_cleanup_local(&key);
+    ossl_cleanup_thread();
 
     /*
      * Note that cleanup order is important:
diff --git a/crypto/initthread.c b/crypto/initthread.c
new file mode 100644
index 0000000..b4ee177
--- /dev/null
+++ b/crypto/initthread.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2019 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/crypto.h>
+#include <openssl/core_numbers.h>
+#include "internal/cryptlib_int.h"
+#include "internal/providercommon.h"
+
+#ifdef FIPS_MODE
+/*
+ * 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.
+ * We call the ossl_init_thread_start function to do that. In the FIPS provider
+ * we have our own copy of ossl_init_thread_start, which cascades notifications
+ * about threads stopping from libcrypto to all the code in the FIPS provider
+ * that needs to know about it.
+ * 
+ * The FIPS provider tells libcrypto about which threads it is interested in
+ * by calling "c_thread_start" which is a function pointer created during
+ * provider initialisation (i.e. OSSL_init_provider).
+ */
+extern OSSL_core_thread_start_fn *c_thread_start;
+#endif
+
+typedef struct thread_event_handler_st THREAD_EVENT_HANDLER;
+struct thread_event_handler_st {
+    void *arg;
+    OSSL_thread_stop_handler_fn handfn;
+    THREAD_EVENT_HANDLER *next;
+};
+
+static void init_thread_stop(void *arg, THREAD_EVENT_HANDLER **hands);
+
+static THREAD_EVENT_HANDLER **
+init_get_thread_local(CRYPTO_THREAD_LOCAL *local, int alloc, int keep)
+{
+    THREAD_EVENT_HANDLER **hands = CRYPTO_THREAD_get_local(local);
+
+    if (alloc) {
+        if (hands == NULL
+            && (hands = OPENSSL_zalloc(sizeof(*hands))) != NULL
+            && !CRYPTO_THREAD_set_local(local, hands)) {
+            OPENSSL_free(hands);
+            return NULL;
+        }
+    } else if (!keep) {
+        CRYPTO_THREAD_set_local(local, NULL);
+    }
+
+    return hands;
+}
+
+#ifndef FIPS_MODE
+/*
+ * Since per-thread-specific-data destructors are not universally
+ * available, i.e. not on Windows, only below CRYPTO_THREAD_LOCAL key
+ * is assumed to have destructor associated. And then an effort is made
+ * to call this single destructor on non-pthread platform[s].
+ *
+ * Initial value is "impossible". It is used as guard value to shortcut
+ * destructor for threads terminating before libcrypto is initialized or
+ * after it's de-initialized. Access to the key doesn't have to be
+ * serialized for the said threads, because they didn't use libcrypto
+ * and it doesn't matter if they pick "impossible" or derefernce real
+ * key value and pull NULL past initialization in the first thread that
+ * intends to use libcrypto.
+ */
+static union {
+    long sane;
+    CRYPTO_THREAD_LOCAL value;
+} destructor_key = { -1 };
+
+static void init_thread_destructor(void *hands)
+{
+    init_thread_stop(NULL, (THREAD_EVENT_HANDLER **)hands);
+    OPENSSL_free(hands);
+}
+
+int ossl_init_thread(void)
+{
+    if (!CRYPTO_THREAD_init_local(&destructor_key.value,
+                                  init_thread_destructor))
+        return 0;
+
+    return 1;
+}
+
+void ossl_cleanup_thread(void)
+{
+    CRYPTO_THREAD_cleanup_local(&destructor_key.value);
+    destructor_key.sane = -1;
+}
+
+void OPENSSL_thread_stop_ex(OPENSSL_CTX *ctx)
+{
+    ctx = openssl_ctx_get_concrete(ctx);
+    /*
+     * TODO(3.0). It would be nice if we could figure out a way to do this on
+     * all threads that have used the OPENSSL_CTX when the OPENSSL_CTX is freed.
+     * This is currently not possible due to the use of thread local variables.
+     */
+    ossl_ctx_thread_stop(ctx);
+}
+
+void OPENSSL_thread_stop(void)
+{
+    if (destructor_key.sane != -1) {
+        THREAD_EVENT_HANDLER **hands
+            = init_get_thread_local(&destructor_key.value, 0, 0);
+        init_thread_stop(NULL, hands);
+        OPENSSL_free(hands);
+    }
+}
+
+void ossl_ctx_thread_stop(void *arg)
+{
+    if (destructor_key.sane != -1) {
+        THREAD_EVENT_HANDLER **hands
+            = init_get_thread_local(&destructor_key.value, 0, 1);
+        init_thread_stop(arg, hands);
+    }
+}
+
+#else
+
+static void *thread_event_ossl_ctx_new(OPENSSL_CTX *libctx)
+{
+    THREAD_EVENT_HANDLER **hands = NULL;
+    CRYPTO_THREAD_LOCAL *tlocal = OPENSSL_zalloc(sizeof(*tlocal));
+
+    if (tlocal == NULL)
+        return NULL;
+
+    if (!CRYPTO_THREAD_init_local(tlocal,  NULL)) {
+        goto err;
+    }
+
+    hands = OPENSSL_zalloc(sizeof(*hands));
+    if (hands == NULL)
+        goto err;
+
+    if (!CRYPTO_THREAD_set_local(tlocal, hands))
+        goto err;
+
+    return tlocal;
+ err:
+    OPENSSL_free(hands);
+    OPENSSL_free(tlocal);
+    return NULL;
+}
+
+static void thread_event_ossl_ctx_free(void *tlocal)
+{
+    OPENSSL_free(tlocal);
+}
+
+static const OPENSSL_CTX_METHOD thread_event_ossl_ctx_method = {
+    thread_event_ossl_ctx_new,
+    thread_event_ossl_ctx_free,
+};
+
+void ossl_ctx_thread_stop(void *arg)
+{
+    THREAD_EVENT_HANDLER **hands;
+    OPENSSL_CTX *ctx = arg;
+    CRYPTO_THREAD_LOCAL *local
+        = openssl_ctx_get_data(ctx, OPENSSL_CTX_THREAD_EVENT_HANDLER_INDEX,
+                               &thread_event_ossl_ctx_method);
+
+    if (local == NULL)
+        return;
+    hands = init_get_thread_local(local, 0, 0);
+    init_thread_stop(arg, hands);
+    OPENSSL_free(hands);
+}
+#endif /* FIPS_MODE */
+
+
+static void init_thread_stop(void *arg, THREAD_EVENT_HANDLER **hands)
+{
+    THREAD_EVENT_HANDLER *curr, *prev = NULL;
+
+    /* Can't do much about this */
+    if (hands == NULL)
+        return;
+
+    curr = *hands;
+    while (curr != NULL) {
+        if (arg != NULL && curr->arg != arg) {
+            curr = curr->next;
+            continue;
+        }
+        curr->handfn(curr->arg);
+        prev = curr;
+        curr = curr->next;
+        if (prev == *hands)
+            *hands = curr;
+        OPENSSL_free(prev);
+    }
+}
+
+int ossl_init_thread_start(void *arg, OSSL_thread_stop_handler_fn handfn)
+{
+    THREAD_EVENT_HANDLER **hands;
+    THREAD_EVENT_HANDLER *hand;
+#ifdef FIPS_MODE
+    OPENSSL_CTX *ctx = arg;
+
+    /*
+     * In FIPS mode the list of THREAD_EVENT_HANDLERs is unique per combination
+     * of OPENSSL_CTX and thread. This is because in FIPS mode each OPENSSL_CTX
+     * gets informed about thread stop events individually.
+     */
+    CRYPTO_THREAD_LOCAL *local
+        = openssl_ctx_get_data(ctx, OPENSSL_CTX_THREAD_EVENT_HANDLER_INDEX,
+                               &thread_event_ossl_ctx_method);
+#else
+    /*
+     * Outside of FIPS mode the list of THREAD_EVENT_HANDLERs is unique per
+     * thread, but may hold multiple OPENSSL_CTXs. We only get told about
+     * thread stop events globally, so we have to ensure all affected
+     * OPENSSL_CTXs are informed.
+     */
+    CRYPTO_THREAD_LOCAL *local = &destructor_key.value;
+#endif
+
+    hands = init_get_thread_local(local, 1, 0);
+    if (hands == NULL)
+        return 0;
+
+#ifdef FIPS_MODE
+    if (*hands == NULL) {
+        /*
+         * We've not yet registered any handlers for this thread. We need to get
+         * libcrypto to tell us about later thread stop events. c_thread_start
+         * is a callback to libcrypto defined in fipsprov.c
+         */
+        if (!c_thread_start(FIPS_get_provider(ctx), ossl_ctx_thread_stop))
+            return 0;
+    }
+#endif
+
+    hand = OPENSSL_malloc(sizeof(*hand));
+    if (hand == NULL)
+        return 0;
+
+    hand->handfn = handfn;
+    hand->arg = arg;
+    hand->next = *hands;
+    *hands = hand;
+
+    return 1;
+}
diff --git a/crypto/provider_core.c b/crypto/provider_core.c
index 62b5bd4..10948ce 100644
--- a/crypto/provider_core.c
+++ b/crypto/provider_core.c
@@ -11,7 +11,7 @@
 #include <openssl/core_numbers.h>
 #include <openssl/params.h>
 #include <openssl/opensslv.h>
-#include "internal/cryptlib.h"
+#include "internal/cryptlib_int.h"
 #include "internal/nelem.h"
 #include "internal/thread_once.h"
 #include "internal/provider.h"
@@ -667,10 +667,17 @@ static OPENSSL_CTX *core_get_libctx(const OSSL_PROVIDER *prov)
     return prov->libctx;
 }
 
+static int core_thread_start(const OSSL_PROVIDER *prov,
+                             OSSL_thread_stop_handler_fn handfn)
+{
+    return ossl_init_thread_start(prov->provctx, handfn);
+}
+
 static const OSSL_DISPATCH core_dispatch_[] = {
     { OSSL_FUNC_CORE_GET_PARAM_TYPES, (void (*)(void))core_get_param_types },
     { OSSL_FUNC_CORE_GET_PARAMS, (void (*)(void))core_get_params },
     { OSSL_FUNC_CORE_GET_LIBRARY_CONTEXT, (void (*)(void))core_get_libctx },
+    { OSSL_FUNC_CORE_THREAD_START, (void (*)(void))core_thread_start },
     { OSSL_FUNC_CORE_PUT_ERROR, (void (*)(void))ERR_put_error },
     { OSSL_FUNC_CORE_ADD_ERROR_VDATA, (void (*)(void))ERR_add_error_vdata },
     { 0, NULL }
diff --git a/crypto/rand/drbg_lib.c b/crypto/rand/drbg_lib.c
index 26e2ccb..5d6ea1e 100644
--- a/crypto/rand/drbg_lib.c
+++ b/crypto/rand/drbg_lib.c
@@ -158,6 +158,14 @@ static void *drbg_ossl_ctx_new(OPENSSL_CTX *libctx)
     if (dgbl == NULL)
         return NULL;
 
+#ifndef FIPS_MODE
+    /*
+     * We need to ensure that base libcrypto thread handling has been
+     * initialised.
+     */
+     OPENSSL_init_crypto(0, NULL);
+#endif
+
     if (!CRYPTO_THREAD_init_local(&dgbl->private_drbg, NULL))
         goto err1;
 
@@ -1137,10 +1145,9 @@ err:
     return NULL;
 }
 
-void drbg_delete_thread_state(void)
+static void drbg_delete_thread_state(void *arg)
 {
-    /* TODO(3.0): Other PRs will pass the ctx as a param to this function */
-    OPENSSL_CTX *ctx = NULL;
+    OPENSSL_CTX *ctx = arg;
     DRBG_GLOBAL *dgbl = drbg_get_global(ctx);
     RAND_DRBG *drbg;
 
@@ -1332,7 +1339,7 @@ RAND_DRBG *OPENSSL_CTX_get0_public_drbg(OPENSSL_CTX *ctx)
 
     drbg = CRYPTO_THREAD_get_local(&dgbl->public_drbg);
     if (drbg == NULL) {
-        if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_RAND))
+        if (!ossl_init_thread_start(NULL, drbg_delete_thread_state))
             return NULL;
         drbg = drbg_setup(ctx, dgbl->master_drbg, RAND_DRBG_TYPE_PUBLIC);
         CRYPTO_THREAD_set_local(&dgbl->public_drbg, drbg);
@@ -1359,7 +1366,7 @@ RAND_DRBG *OPENSSL_CTX_get0_private_drbg(OPENSSL_CTX *ctx)
 
     drbg = CRYPTO_THREAD_get_local(&dgbl->private_drbg);
     if (drbg == NULL) {
-        if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_RAND))
+        if (!ossl_init_thread_start(NULL, drbg_delete_thread_state))
             return NULL;
         drbg = drbg_setup(ctx, dgbl->master_drbg, RAND_DRBG_TYPE_PRIVATE);
         CRYPTO_THREAD_set_local(&dgbl->private_drbg, drbg);
diff --git a/doc/man3/OPENSSL_CTX.pod b/doc/man3/OPENSSL_CTX.pod
index 5348367..1893c92 100644
--- a/doc/man3/OPENSSL_CTX.pod
+++ b/doc/man3/OPENSSL_CTX.pod
@@ -21,6 +21,10 @@ the internal default context with functions that take a C<OPENSSL_CTX>
 argument.
 
 OPENSSL_CTX_new() creates a new OpenSSL library context.
+When a non default library context is in use care should be taken with
+multi-threaded applications to properly clean up thread local resources before
+the OPENSSL_CTX is freed.
+See L<OPENSSL_thread_stop_ex(3)> for more information.
 
 OPENSSL_CTX_free() frees the given C<ctx>.
 
diff --git a/doc/man3/OPENSSL_init_crypto.pod b/doc/man3/OPENSSL_init_crypto.pod
index a832561..e20fc8c 100644
--- a/doc/man3/OPENSSL_init_crypto.pod
+++ b/doc/man3/OPENSSL_init_crypto.pod
@@ -5,7 +5,7 @@
 OPENSSL_INIT_new, OPENSSL_INIT_set_config_filename,
 OPENSSL_INIT_set_config_appname, OPENSSL_INIT_set_config_file_flags,
 OPENSSL_INIT_free, OPENSSL_init_crypto, OPENSSL_cleanup, OPENSSL_atexit,
-OPENSSL_thread_stop - OpenSSL initialisation
+OPENSSL_thread_stop_ex, OPENSSL_thread_stop - OpenSSL initialisation
 and deinitialisation functions
 
 =head1 SYNOPSIS
@@ -15,6 +15,7 @@ and deinitialisation functions
  void OPENSSL_cleanup(void);
  int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings);
  int OPENSSL_atexit(void (*handler)(void));
+ void OPENSSL_thread_stop_ex(OPENSSL_CTX *ctx);
  void OPENSSL_thread_stop(void);
 
  OPENSSL_INIT_SETTINGS *OPENSSL_INIT_new(void);
@@ -202,11 +203,25 @@ called after deinitialisation of resources local to a thread, but before other
 process wide resources are freed. In the event that multiple stop handlers are
 registered, no guarantees are made about the order of execution.
 
-The OPENSSL_thread_stop() function deallocates resources associated
-with the current thread. Typically this function will be called automatically by
-the library when the thread exits. This should only be called directly if
-resources should be freed at an earlier time, or under the circumstances
-described in the NOTES section below.
+The OPENSSL_thread_stop_ex() function deallocates resources associated
+with the current thread for the given OPENSSL_CTX B<ctx>. The B<ctx> parameter
+can be NULL in which case the default OPENSSL_CTX is used.
+
+Typically, this function will be called automatically by the library when
+the thread exits as long as the OPENSSL_CTX has not been freed before the thread
+exits. If OPENSSL_CTX_free() is called OPENSSL_thread_stop_ex will be called
+automatically for the current thread (but not any other threads that may have
+used this OPENSSL_CTX).
+
+OPENSSL_thread_stop_ex should be called on all threads that will exit after the
+OPENSSL_CTX is freed.
+Typically this is not necessary for the default OPENSSL_CTX (because all
+resources are cleaned up on library exit) except if thread local resources
+should be freed before library exit, or under the circumstances described in
+the NOTES section below.
+
+OPENSSL_thread_stop() is the same as OPENSSL_thread_stop_ex() except that the
+default OPENSSL_CTX is always used.
 
 The B<OPENSSL_INIT_LOAD_CONFIG> flag will load a configuration file, as with
 L<CONF_modules_load_file(3)> with NULL filename and application name and the
diff --git a/include/internal/cryptlib.h b/include/internal/cryptlib.h
index d76f9e1..f7bd06b 100644
--- a/include/internal/cryptlib.h
+++ b/include/internal/cryptlib.h
@@ -149,13 +149,17 @@ typedef struct ossl_ex_data_global_st {
 # define OPENSSL_CTX_DRBG_INDEX                     5
 # define OPENSSL_CTX_DRBG_NONCE_INDEX               6
 # define OPENSSL_CTX_RAND_CRNGT_INDEX               7
-# define OPENSSL_CTX_MAX_INDEXES                    8
+# define OPENSSL_CTX_THREAD_EVENT_HANDLER_INDEX     8
+# define OPENSSL_CTX_FIPS_PROV_INDEX                9
+# define OPENSSL_CTX_MAX_INDEXES                   10
 
 typedef struct openssl_ctx_method {
     void *(*new_func)(OPENSSL_CTX *ctx);
     void (*free_func)(void *);
 } OPENSSL_CTX_METHOD;
 
+OPENSSL_CTX *openssl_ctx_get_concrete(OPENSSL_CTX *ctx);
+
 /* Functions to retrieve pointers to data by index */
 void *openssl_ctx_get_data(OPENSSL_CTX *, int /* index */,
                            const OPENSSL_CTX_METHOD * ctx);
diff --git a/include/openssl/core.h b/include/openssl/core.h
index cf4d3f4..f596957 100644
--- a/include/openssl/core.h
+++ b/include/openssl/core.h
@@ -143,6 +143,19 @@ struct ossl_param_st {
  */
 # define OSSL_PARAM_OCTET_PTR            7
 
+/*
+ * Typedef for the thread stop handling callback. Used both internally and by
+ * providers.
+ * 
+ * Providers may register for notifications about threads stopping by
+ * registering a callback to hear about such events. Providers register the
+ * callback using the OSSL_FUNC_CORE_THREAD_START function in the |in| dispatch
+ * table passed to OSSL_provider_init(). The arg passed back to a provider will
+ * be the provider side context object.
+ */
+typedef void (*OSSL_thread_stop_handler_fn)(void *arg);
+
+
 /*-
  * Provider entry point
  * --------------------
diff --git a/include/openssl/core_numbers.h b/include/openssl/core_numbers.h
index 370e059..e1f0200 100644
--- a/include/openssl/core_numbers.h
+++ b/include/openssl/core_numbers.h
@@ -58,12 +58,15 @@ OSSL_CORE_MAKE_FUNC(const OSSL_ITEM *,
 # define OSSL_FUNC_CORE_GET_PARAMS             2
 OSSL_CORE_MAKE_FUNC(int,core_get_params,(const OSSL_PROVIDER *prov,
                                          const OSSL_PARAM params[]))
-# define OSSL_FUNC_CORE_PUT_ERROR              3
+# define OSSL_FUNC_CORE_THREAD_START           3
+OSSL_CORE_MAKE_FUNC(int,core_thread_start,(const OSSL_PROVIDER *prov,
+                                           OSSL_thread_stop_handler_fn handfn))
+# define OSSL_FUNC_CORE_PUT_ERROR              4
 OSSL_CORE_MAKE_FUNC(void,core_put_error,(int lib, int func, int reason,
                                          const char *file, int line))
-# define OSSL_FUNC_CORE_ADD_ERROR_VDATA        4
+# define OSSL_FUNC_CORE_ADD_ERROR_VDATA        5
 OSSL_CORE_MAKE_FUNC(void,core_add_error_vdata,(int num, va_list args))
-# define OSSL_FUNC_CORE_GET_LIBRARY_CONTEXT    5
+# define OSSL_FUNC_CORE_GET_LIBRARY_CONTEXT    6
 OSSL_CORE_MAKE_FUNC(OPENSSL_CTX *,core_get_library_context,
                     (const OSSL_PROVIDER *prov))
 
diff --git a/include/openssl/crypto.h b/include/openssl/crypto.h
index 5322cc8..7953119 100644
--- a/include/openssl/crypto.h
+++ b/include/openssl/crypto.h
@@ -415,6 +415,7 @@ void OPENSSL_cleanup(void);
 int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings);
 int OPENSSL_atexit(void (*handler)(void));
 void OPENSSL_thread_stop(void);
+void OPENSSL_thread_stop_ex(OPENSSL_CTX *ctx);
 
 /* Low-level control of initialization */
 OPENSSL_INIT_SETTINGS *OPENSSL_INIT_new(void);
diff --git a/providers/common/include/internal/providercommon.h b/providers/common/include/internal/providercommon.h
index e69de29..663d9c6 100644
--- a/providers/common/include/internal/providercommon.h
+++ b/providers/common/include/internal/providercommon.h
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2019 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
+ */
+
+const OSSL_PROVIDER *FIPS_get_provider(OPENSSL_CTX *ctx);
diff --git a/providers/fips/fipsprov.c b/providers/fips/fipsprov.c
index 51246d5..9f9b428 100644
--- a/providers/fips/fipsprov.c
+++ b/providers/fips/fipsprov.c
@@ -22,13 +22,44 @@
 #include "internal/evp_int.h"
 #include "internal/provider_algs.h"
 #include "internal/provider_ctx.h"
+#include "internal/providercommon.h"
 
+/*
+ * TODO(3.0): Should these be stored in the provider side provctx? Could they
+ * ever be different from one init to the next? Unfortunately we can't do this
+ * at the moment because c_put_error/c_add_error_vdata do not provide us with
+ * the OPENSSL_CTX as a parameter.
+ */
 /* Functions provided by the core */
 static OSSL_core_get_param_types_fn *c_get_param_types = NULL;
 static OSSL_core_get_params_fn *c_get_params = NULL;
+extern OSSL_core_thread_start_fn *c_thread_start;
+OSSL_core_thread_start_fn *c_thread_start = NULL;
 static OSSL_core_put_error_fn *c_put_error = NULL;
 static OSSL_core_add_error_vdata_fn *c_add_error_vdata = NULL;
 
+typedef struct fips_global_st {
+    const OSSL_PROVIDER *prov;
+} FIPS_GLOBAL;
+
+static void *fips_prov_ossl_ctx_new(OPENSSL_CTX *libctx)
+{
+    FIPS_GLOBAL *fgbl = OPENSSL_zalloc(sizeof(*fgbl));
+
+    return fgbl;
+}
+
+static void fips_prov_ossl_ctx_free(void *fgbl)
+{
+    OPENSSL_free(fgbl);
+}
+
+static const OPENSSL_CTX_METHOD fips_prov_ossl_ctx_method = {
+    fips_prov_ossl_ctx_new,
+    fips_prov_ossl_ctx_free,
+};
+
+
 /* Parameters we provide to the core */
 static const OSSL_ITEM fips_param_types[] = {
     { OSSL_PARAM_UTF8_PTR, OSSL_PROV_PARAM_NAME },
@@ -184,7 +215,19 @@ int OSSL_provider_init(const OSSL_PROVIDER *provider,
                        const OSSL_DISPATCH **out,
                        void **provctx)
 {
-    OPENSSL_CTX *ctx;
+    FIPS_GLOBAL *fgbl;
+    OPENSSL_CTX *ctx = OPENSSL_CTX_new();
+
+    if (ctx == NULL)
+        return 0;
+
+    fgbl = openssl_ctx_get_data(ctx, OPENSSL_CTX_FIPS_PROV_INDEX,
+                                &fips_prov_ossl_ctx_method);
+
+    if (fgbl == NULL)
+        goto err;
+
+    fgbl->prov = provider;
 
     for (; in->function_id != 0; in++) {
         switch (in->function_id) {
@@ -194,6 +237,9 @@ int OSSL_provider_init(const OSSL_PROVIDER *provider,
         case OSSL_FUNC_CORE_GET_PARAMS:
             c_get_params = OSSL_get_core_get_params(in);
             break;
+        case OSSL_FUNC_CORE_THREAD_START:
+            c_thread_start = OSSL_get_core_thread_start(in);
+            break;
         case OSSL_FUNC_CORE_PUT_ERROR:
             c_put_error = OSSL_get_core_put_error(in);
             break;
@@ -224,6 +270,10 @@ int OSSL_provider_init(const OSSL_PROVIDER *provider,
     }
 
     return 1;
+
+ err:
+    OPENSSL_CTX_free(ctx);
+    return 0;
 }
 
 /*
@@ -290,3 +340,14 @@ void ERR_add_error_vdata(int num, va_list args)
 {
     c_add_error_vdata(num, args);
 }
+
+const OSSL_PROVIDER *FIPS_get_provider(OPENSSL_CTX *ctx)
+{
+    FIPS_GLOBAL *fgbl = openssl_ctx_get_data(ctx, OPENSSL_CTX_FIPS_PROV_INDEX,
+                                             &fips_prov_ossl_ctx_method);
+
+    if (fgbl == NULL)
+        return NULL;
+
+    return fgbl->prov;
+}
diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index 6fca3a3..5bf4a8c 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -1220,6 +1220,11 @@ static int test_EVP_MD_fetch(int tst)
     EVP_MD_meth_free(md);
     OSSL_PROVIDER_unload(defltprov);
     OSSL_PROVIDER_unload(fipsprov);
+    /* Not normally needed, but we would like to test that
+     * OPENSSL_thread_stop_ex() behaves as expected.
+     */
+    if (ctx != NULL)
+        OPENSSL_thread_stop_ex(ctx);
     OPENSSL_CTX_free(ctx);
     return ret;
 }
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 7280649..0b18107 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -4831,3 +4831,4 @@ OPENSSL_CTX_get0_public_drbg            4775	3_0_0	EXIST::FUNCTION:
 OPENSSL_CTX_get0_private_drbg           4776	3_0_0	EXIST::FUNCTION:
 BN_CTX_new_ex                           4777	3_0_0	EXIST::FUNCTION:
 BN_CTX_secure_new_ex                    4778	3_0_0	EXIST::FUNCTION:
+OPENSSL_thread_stop_ex                  4779	3_0_0	EXIST::FUNCTION:


More information about the openssl-commits mailing list