| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 | /* * Copyright 2019-2023 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_dispatch.h>#include "crypto/cryptlib.h"#include "prov/providercommon.h"#include "internal/thread_once.h"#include "crypto/context.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. * 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_provider_init). */extern OSSL_FUNC_core_thread_start_fn *c_thread_start;#endiftypedef struct thread_event_handler_st THREAD_EVENT_HANDLER;struct thread_event_handler_st {#ifndef FIPS_MODULE    const void *index;#endif    void *arg;    OSSL_thread_stop_handler_fn handfn;    THREAD_EVENT_HANDLER *next;};#ifndef FIPS_MODULEDEFINE_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_MODULEstatic 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);#endifstatic 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_MODULE            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_MODULE/* * 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;    if (!CRYPTO_THREAD_write_lock(gtr->lock))        return 0;    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;    if (!CRYPTO_THREAD_write_lock(gtr->lock))        return;    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) {            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(OSSL_LIB_CTX *ctx){    ctx = ossl_lib_ctx_get_concrete(ctx);    /*     * It would be nice if we could figure out a way to do this on all threads     * that have used the OSSL_LIB_CTX when the context 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(OSSL_LIB_CTX *ctx){    if (destructor_key.sane != -1) {        THREAD_EVENT_HANDLER **hands            = init_get_thread_local(&destructor_key.value, 0, 1);        init_thread_stop(ctx, hands);    }}#elsestatic void ossl_arg_thread_stop(void *arg);/* Register the current thread so that we are informed if it gets stopped */int ossl_thread_register_fips(OSSL_LIB_CTX *libctx){    return c_thread_start(FIPS_get_core_handle(libctx), ossl_arg_thread_stop,                          libctx);}void *ossl_thread_event_ctx_new(OSSL_LIB_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;    /*     * We should ideally call ossl_thread_register_fips() here. This function     * is called during the startup of the FIPS provider and we need to ensure     * that the main thread is registered to receive thread callbacks in order     * to free |hands| that we allocated above. However we are too early in     * the FIPS provider initialisation that FIPS_get_core_handle() doesn't work     * yet. So we defer this to the main provider OSSL_provider_init_int()     * function.     */    return tlocal; err:    OPENSSL_free(hands);    OPENSSL_free(tlocal);    return NULL;}void ossl_thread_event_ctx_free(void *tlocal){    OPENSSL_free(tlocal);}static void ossl_arg_thread_stop(void *arg){    ossl_ctx_thread_stop((OSSL_LIB_CTX *)arg);}void ossl_ctx_thread_stop(OSSL_LIB_CTX *ctx){    THREAD_EVENT_HANDLER **hands;    CRYPTO_THREAD_LOCAL *local        = ossl_lib_ctx_get_data(ctx, OSSL_LIB_CTX_THREAD_EVENT_HANDLER_INDEX);    if (local == NULL)        return;    hands = init_get_thread_local(local, 0, 0);    init_thread_stop(ctx, hands);    OPENSSL_free(hands);}#endif /* FIPS_MODULE */static void init_thread_stop(void *arg, THREAD_EVENT_HANDLER **hands){    THREAD_EVENT_HANDLER *curr, *prev = NULL, *tmp;#ifndef FIPS_MODULE    GLOBAL_TEVENT_REGISTER *gtr;#endif    /* Can't do much about this */    if (hands == NULL)        return;#ifndef FIPS_MODULE    gtr = get_global_tevent_register();    if (gtr == NULL)        return;    if (!CRYPTO_THREAD_write_lock(gtr->lock))        return;#endif    curr = *hands;    while (curr != NULL) {        if (arg != NULL && curr->arg != arg) {            prev = curr;            curr = curr->next;            continue;        }        curr->handfn(curr->arg);        if (prev == NULL)            *hands = curr->next;        else            prev->next = curr->next;        tmp = curr;        curr = curr->next;        OPENSSL_free(tmp);    }#ifndef FIPS_MODULE    CRYPTO_THREAD_unlock(gtr->lock);#endif}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_MODULE    OSSL_LIB_CTX *ctx = arg;    /*     * In FIPS mode the list of THREAD_EVENT_HANDLERs is unique per combination     * of OSSL_LIB_CTX and thread. This is because in FIPS mode each     * OSSL_LIB_CTX gets informed about thread stop events individually.     */    CRYPTO_THREAD_LOCAL *local        = ossl_lib_ctx_get_data(ctx, OSSL_LIB_CTX_THREAD_EVENT_HANDLER_INDEX);#else    /*     * Outside of FIPS mode the list of THREAD_EVENT_HANDLERs is unique per     * thread, but may hold multiple OSSL_LIB_CTXs. We only get told about     * thread stop events globally, so we have to ensure all affected     * OSSL_LIB_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_MODULE    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 (!ossl_thread_register_fips(ctx))            return 0;    }#endif    hand = OPENSSL_malloc(sizeof(*hand));    if (hand == NULL)        return 0;    hand->handfn = handfn;    hand->arg = arg;#ifndef FIPS_MODULE    hand->index = index;#endif    hand->next = *hands;    *hands = hand;    return 1;}#ifndef FIPS_MODULEstatic int init_thread_deregister(void *index, int all){    GLOBAL_TEVENT_REGISTER *gtr;    int i;    gtr = get_global_tevent_register();    if (gtr == NULL)        return 0;    if (!all) {        if (!CRYPTO_THREAD_write_lock(gtr->lock))            return 0;    } else {        glob_tevent_reg = NULL;    }    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 = NULL, *prev = NULL, *tmp;        if (hands == NULL) {            if (!all)                CRYPTO_THREAD_unlock(gtr->lock);            return 0;        }        curr = *hands;        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
 |