openssl/crypto/initthread.c
Dr. Matthias St. Pierre 3b438ef95b Fix init_get_thread_local()
Previously, init_get_thread_local() pushed the thread event handler
list onto the global register before calling CRYPTO_THREAD_set_local(),
and when the latter failed, forgot to pop the list from the stack again.

Instead of cleaning the stack on error, this commit avoids the situation
entirely by postponing the push operation until all other operations
succeeded. This reordering also significantly reduces the scope of the
critical section.

Another simplification of the code is achieved by moving the push operation
onto the register (which is disabled in FIPS mode) into a separate function.

Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/9401)
2019-07-18 06:20:55 +02:00

427 lines
12 KiB
C

/*
* 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"
#include "internal/thread_once.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 {
const void *index;
void *arg;
OSSL_thread_stop_handler_fn handfn;
THREAD_EVENT_HANDLER *next;
};
#ifndef FIPS_MODE
DEFINE_SPECIAL_STACK_OF(THREAD_EVENT_HANDLER_PTR, THREAD_EVENT_HANDLER *)
typedef struct global_tevent_register_st GLOBAL_TEVENT_REGISTER;
struct global_tevent_register_st {
STACK_OF(THREAD_EVENT_HANDLER_PTR) *skhands;
CRYPTO_RWLOCK *lock;
};
static GLOBAL_TEVENT_REGISTER *glob_tevent_reg = NULL;
static CRYPTO_ONCE tevent_register_runonce = CRYPTO_ONCE_STATIC_INIT;
DEFINE_RUN_ONCE_STATIC(create_global_tevent_register)
{
glob_tevent_reg = OPENSSL_zalloc(sizeof(*glob_tevent_reg));
if (glob_tevent_reg == NULL)
return 0;
glob_tevent_reg->skhands = sk_THREAD_EVENT_HANDLER_PTR_new_null();
glob_tevent_reg->lock = CRYPTO_THREAD_lock_new();
if (glob_tevent_reg->skhands == NULL || glob_tevent_reg->lock == NULL) {
sk_THREAD_EVENT_HANDLER_PTR_free(glob_tevent_reg->skhands);
CRYPTO_THREAD_lock_free(glob_tevent_reg->lock);
OPENSSL_free(glob_tevent_reg);
glob_tevent_reg = NULL;
return 0;
}
return 1;
}
static GLOBAL_TEVENT_REGISTER *get_global_tevent_register(void)
{
if (!RUN_ONCE(&tevent_register_runonce, create_global_tevent_register))
return NULL;
return glob_tevent_reg;
}
#endif
#ifndef FIPS_MODE
static int init_thread_push_handlers(THREAD_EVENT_HANDLER **hands);
static void init_thread_remove_handlers(THREAD_EVENT_HANDLER **handsin);
static void init_thread_destructor(void *hands);
static int init_thread_deregister(void *arg, int all);
#endif
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) {
if ((hands = OPENSSL_zalloc(sizeof(*hands))) == NULL)
return NULL;
if (!CRYPTO_THREAD_set_local(local, hands)) {
OPENSSL_free(hands);
return NULL;
}
#ifndef FIPS_MODE
if (!init_thread_push_handlers(hands)) {
CRYPTO_THREAD_set_local(local, NULL);
OPENSSL_free(hands);
return NULL;
}
#endif
}
} 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 dereference 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 };
/*
* The thread event handler list is a thread specific linked list
* of callback functions which are invoked in list order by the
* current thread in case of certain events. (Currently, there is
* only one type of event, the 'thread stop' event.)
*
* We also keep a global reference to that linked list, so that we
* can deregister handlers if necessary before all the threads are
* stopped.
*/
static int init_thread_push_handlers(THREAD_EVENT_HANDLER **hands)
{
int ret;
GLOBAL_TEVENT_REGISTER *gtr;
gtr = get_global_tevent_register();
if (gtr == NULL)
return 0;
CRYPTO_THREAD_write_lock(gtr->lock);
ret = (sk_THREAD_EVENT_HANDLER_PTR_push(gtr->skhands, hands) != 0);
CRYPTO_THREAD_unlock(gtr->lock);
return ret;
}
static void init_thread_remove_handlers(THREAD_EVENT_HANDLER **handsin)
{
GLOBAL_TEVENT_REGISTER *gtr;
int i;
gtr = get_global_tevent_register();
if (gtr == NULL)
return;
CRYPTO_THREAD_write_lock(gtr->lock);
for (i = 0; i < sk_THREAD_EVENT_HANDLER_PTR_num(gtr->skhands); i++) {
THREAD_EVENT_HANDLER **hands
= sk_THREAD_EVENT_HANDLER_PTR_value(gtr->skhands, i);
if (hands == handsin) {
hands = sk_THREAD_EVENT_HANDLER_PTR_delete(gtr->skhands, i);
CRYPTO_THREAD_unlock(gtr->lock);
return;
}
}
CRYPTO_THREAD_unlock(gtr->lock);
return;
}
static void init_thread_destructor(void *hands)
{
init_thread_stop(NULL, (THREAD_EVENT_HANDLER **)hands);
init_thread_remove_handlers(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)
{
init_thread_deregister(NULL, 1);
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);
init_thread_remove_handlers(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(const void *index, 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->index = index;
hand->next = *hands;
*hands = hand;
return 1;
}
#ifndef FIPS_MODE
static int init_thread_deregister(void *index, int all)
{
GLOBAL_TEVENT_REGISTER *gtr;
int i;
gtr = get_global_tevent_register();
if (!all)
CRYPTO_THREAD_write_lock(gtr->lock);
for (i = 0; i < sk_THREAD_EVENT_HANDLER_PTR_num(gtr->skhands); i++) {
THREAD_EVENT_HANDLER **hands
= sk_THREAD_EVENT_HANDLER_PTR_value(gtr->skhands, i);
THREAD_EVENT_HANDLER *curr = *hands, *prev = NULL, *tmp;
if (hands == NULL) {
if (!all)
CRYPTO_THREAD_unlock(gtr->lock);
return 0;
}
while (curr != NULL) {
if (all || curr->index == index) {
if (prev != NULL)
prev->next = curr->next;
else
*hands = curr->next;
tmp = curr;
curr = curr->next;
OPENSSL_free(tmp);
continue;
}
prev = curr;
curr = curr->next;
}
if (all)
OPENSSL_free(hands);
}
if (all) {
CRYPTO_THREAD_lock_free(gtr->lock);
sk_THREAD_EVENT_HANDLER_PTR_free(gtr->skhands);
OPENSSL_free(gtr);
} else {
CRYPTO_THREAD_unlock(gtr->lock);
}
return 1;
}
int ossl_init_thread_deregister(void *index)
{
return init_thread_deregister(index, 0);
}
#endif