From: Neil Horman Date: Thu, 12 Jun 2025 17:09:56 +0000 (-0400) Subject: Add new CRYPTO_THREAD_[get|set]_local_ex api X-Git-Tag: openssl-3.6.0-alpha1~559 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c1c2a333d34871f6210f2b5fb21ee631c4fb319e;p=thirdparty%2Fopenssl.git Add new CRYPTO_THREAD_[get|set]_local_ex api As opposed to CRYPTO_THREAD_[get|set]_local counterparts These api use an ennumerated set of fixed key ids, that allow for thread-local storage indexed by key AND libctx value. They also store this data against a single OS level thread-local key, reducing the amount of (limited) thread-local key storage space we use Reviewed-by: Saša Nedvědický Reviewed-by: Tomas Mraz Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/27794) --- diff --git a/crypto/build.info b/crypto/build.info index fd160826337..e0e481ac76e 100644 --- a/crypto/build.info +++ b/crypto/build.info @@ -97,10 +97,10 @@ SOURCE[../providers/libfips.a]=$CORE_COMMON # Central utilities $UTIL_COMMON=\ cryptlib.c params.c params_from_text.c bsearch.c ex_data.c o_str.c \ - threads_pthread.c threads_win.c threads_none.c initthread.c \ - context.c sparse_array.c asn1_dsa.c packet.c param_build.c \ - param_build_set.c der_writer.c threads_lib.c params_dup.c \ - time.c + threads_pthread.c threads_win.c threads_none.c threads_common.c \ + initthread.c context.c sparse_array.c asn1_dsa.c packet.c \ + param_build.c param_build_set.c der_writer.c threads_lib.c \ + params_dup.c time.c SOURCE[../libcrypto]=$UTIL_COMMON \ mem.c mem_sec.c \ diff --git a/crypto/threads_common.c b/crypto/threads_common.c new file mode 100644 index 00000000000..27c42a97aec --- /dev/null +++ b/crypto/threads_common.c @@ -0,0 +1,452 @@ +/* + * Copyright 2025 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 + */ + +/** + * @file + * @brief Thread-local context-specific data management for OpenSSL + * + * This file implements a mechanism to store and retrieve context-specific + * data using OpenSSL's thread-local storage (TLS) system. It provides a way + * to associate and manage data based on a combination of a thread-local key + * and an `OSSL_LIB_CTX *` context. + * + * NOTE: This differs from the CRYPTO_THREAD_[get|set]_local api set in that + * this api stores a single OS level thread-local key per-process, and manages + * subsequent keys using a series of sparse arrays stored against that aforementioned + * thread local key + * + * Data Design: + * + * per-thread master key data -> +--------------+-+ + * | | | + * | | | + * +--------------+ | + * +--------------+ | + * | | | + * | | | + * +--------------+ | sparse array indexed + * . | by key id + * . | + * . | + * +--------------+ | + * | | | + * | | | | + * +-------+------+-+ + * | + * ++---------------+ | + * || |<----------+ + * || | + * |+---------------+ + * |+---------------+ + * || | + * || | sparse array indexed + * |+---------------+ by libctx pointer + * | . cast to uintptr_t + * | . + * | . + * |+---------------+ + * || +------+----> +-----------------+ + * || | | | | + * ++--------+------+ +-----------------+ + * per- data + * + * It uses sparse arrays to map: + * - Thread-local key IDs to master key entries. + * - Library context pointers to context-specific data. + * + * Two primary functions are provided: + * - CRYPTO_THREAD_get_local_ex() retrieves data associated with a key and + * context. + * - CRYPTO_THREAD_set_local_ex() associates data with a given key and + * context, allocating tables as needed. + * + * Internal structures: + * - CTX_TABLE_ENTRY: wraps a context-specific data pointer. + * - MASTER_KEY_ENTRY: maintains a table of CTX_TABLE_ENTRY and an optional + * cleanup function. + * + * The implementation ensures: + * - Lazy initialization of master key data using CRYPTO_ONCE. + * - Automatic cleanup of all context and key mappings when a thread exits. + * + * Cleanup routines: + * - clean_ctx_entry: releases context-specific entries. + * - clean_master_key_id: releases all entries for a specific key ID. + * - clean_master_key: top-level cleanup for the thread-local master key. + * + */ + +#include +#include +#include +#include "internal/cryptlib.h" +#include "internal/threads_common.h" + +/** + * @struct CTX_TABLE_ENTRY + * @brief Represents a wrapper for context-specific data. + * + * This structure is used to hold a pointer to data that is associated + * with a particular `OSSL_LIB_CTX` instance in a thread-local context + * mapping. It is stored within a sparse array, allowing efficient + * per-context data lookup keyed by a context identifier. + * + * @var CTX_TABLE_ENTRY::ctx_data + * Pointer to the data associated with a given library context. + */ +typedef struct ctx_table_entry { + void *ctx_data; +} CTX_TABLE_ENTRY; + +/* + * define our sparse array of CTX_TABLE_ENTRY functions + */ +DEFINE_SPARSE_ARRAY_OF(CTX_TABLE_ENTRY); + +/** + * @struct MASTER_KEY_ENTRY + * @brief Represents a mapping of context-specific data for a TLS key ID. + * + * This structure manages a collection of `CTX_TABLE_ENTRY` items, each + * associated with a different `OSSL_LIB_CTX` instance. It supports + * cleanup of stored data when the thread or key is being destroyed. + * + * @var MASTER_KEY_ENTRY::ctx_table + * Sparse array mapping `OSSL_LIB_CTX` pointers (cast to uintptr_t) to + * `CTX_TABLE_ENTRY` structures that hold context-specific data. + * + */ +typedef struct master_key_entry { + SPARSE_ARRAY_OF(CTX_TABLE_ENTRY) *ctx_table; +} MASTER_KEY_ENTRY; + +/* + * Define our sparse array functions for MASTER_KEY_ENTRY structs + */ +DEFINE_SPARSE_ARRAY_OF(MASTER_KEY_ENTRY); + +/** + * @brief holds our per thread data with the operating system + * + * Global thread local storage pointer, used to create a platform + * specific thread-local key + */ +static CRYPTO_THREAD_LOCAL master_key; + +/** + * @brief Informs the library if the master key has been set up + * + * State variable to track if we have initialized the master_key + * If this isn't set to 1, then we need to skip any cleanup + * in CRYPTO_THREAD_clean_for_fips, as the uninitialized key + * will return garbage data + */ +static uint8_t master_key_init = 0; + +/** + * @brief gate variable to do one time init of the master key + * + * Run once gate for doing one-time initialization + */ +static CRYPTO_ONCE master_once = CRYPTO_ONCE_STATIC_INIT; + +/** + * @brief Cleans up a single context-specific entry. + * + * This function is used as a callback in sparse array traversal to free + * a `CTX_TABLE_ENTRY`. If a cleanup function is defined in the associated + * `MASTER_KEY_ENTRY`, it is called prior to freeing the entry. + * + * @param idx + * Unused index value corresponding to the key in the sparse array. + * + * @param ctxentry + * Pointer to the `CTX_TABLE_ENTRY` that holds the context-specific + * data to be freed. + * + * @param arg + * Pointer to the parent `MASTER_KEY_ENTRY` which may contain a + * cleanup function. + */ +static void clean_ctx_entry(ossl_uintmax_t idx, CTX_TABLE_ENTRY *ctxentry, void *arg) +{ + OPENSSL_free(ctxentry); +} + +/** + * @brief Cleans up all context-specific entries for a given key ID. + * + * This function is used to release all context data associated with a + * specific thread-local key (identified by `idx`). It iterates over the + * context table in the given `MASTER_KEY_ENTRY`, invoking cleanup for each + * `CTX_TABLE_ENTRY`, then frees the context table and the entry itself. + * + * @param idx + * The key ID associated with the `MASTER_KEY_ENTRY`. Unused. + * + * @param entry + * Pointer to the `MASTER_KEY_ENTRY` containing the context table + * to be cleaned up. + * + * @param arg + * Unused parameter. + */ +static void clean_master_key_id(ossl_uintmax_t idx, MASTER_KEY_ENTRY *entry, void *arg) +{ + ossl_sa_CTX_TABLE_ENTRY_doall_arg(entry->ctx_table, clean_ctx_entry, entry); + ossl_sa_CTX_TABLE_ENTRY_free(entry->ctx_table); + OPENSSL_free(entry); +} + +/** + * @brief Cleans up all master key entries for the current thread. + * + * This function is the top-level cleanup routine for the thread-local + * storage associated with OpenSSL master keys. It is typically registered + * as the thread-local storage destructor. It iterates over all + * `MASTER_KEY_ENTRY` items in the sparse array, releasing associated + * context data and structures. + * + * @param data + * Pointer to the thread-local `SPARSE_ARRAY_OF(MASTER_KEY_ENTRY)` + * structure to be cleaned up. + */ +static void clean_master_key(void *data) +{ + SPARSE_ARRAY_OF(MASTER_KEY_ENTRY) *mkey = data; + + ossl_sa_MASTER_KEY_ENTRY_doall_arg(mkey, clean_master_key_id, NULL); + ossl_sa_MASTER_KEY_ENTRY_free(mkey); +} + +/** + * @brief Initializes the thread-local storage for master key data. + * + * This function sets up the thread-local key used to store per-thread + * master key tables. It also registers the `clean_master_key` function + * as the destructor to be called when the thread exits. + * + * This function is intended to be called once using `CRYPTO_THREAD_run_once` + * to ensure thread-safe initialization. + */ +static void init_master_key(void) +{ + /* + * Note: We assign a cleanup function here, which is atypical for + * uses of CRYPTO_THREAD_init_local. This is because, nominally + * we expect that the use of ossl_init_thread_start will be used + * to notify openssl of exiting threads. However, in this case + * we want the metadata for this interface (the sparse arrays) to + * stay valid until the thread actually exits, which is what the + * clean_master_key function does. Data held in the sparse arrays + * (that is assigned via CRYPTO_THREAD_set_local_ex), are still expected + * to be cleaned via the ossl_init_thread_start/stop api. + */ + CRYPTO_THREAD_init_local(&master_key, clean_master_key); + + /* + * Indicate that the key has been set up. + */ + master_key_init = 1; +} + +/** + * @brief Retrieves context-specific data from thread-local storage. + * + * This function looks up and returns the data associated with a given + * thread-local key ID and `OSSL_LIB_CTX` context. The data must have + * previously been stored using `CRYPTO_THREAD_set_local_ex()`. + * + * If the master key table is not yet initialized, it will be lazily + * initialized via `init_master_key()`. If the requested key or context + * entry does not exist, `NULL` is returned. + * + * @param id + * The thread-local key ID used to identify the master key entry. + * + * @param ctx + * Pointer to the `OSSL_LIB_CTX` used to index into the context + * table for the specified key. + * + * @return A pointer to the stored context-specific data, or NULL if no + * entry is found or initialization fails. + */ +void *CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id, OSSL_LIB_CTX *ctx) +{ + SPARSE_ARRAY_OF(MASTER_KEY_ENTRY) *mkey; + MASTER_KEY_ENTRY *entry; + CTX_TABLE_ENTRY *ctxd; + + /* + * Make sure the master key has been initialized + * NOTE: We use CRYPTO_THREAD_run_once here, rather than the + * RUN_ONCE macros. We do this because this code is included both in + * libcrypto, and in fips.[dll|dylib|so]. FIPS attempts to avoid doing + * one time initialization of global data, and so suppresses the definition + * of RUN_ONCE, etc, meaning the build breaks if we were to use that with + * fips-enabled. However, this is a special case in which we want/need + * this one bit of global data to be initialized in both the fips provider + * and in libcrypto, so we use CRYPTO_THREAD_run_one directly, which is + * always defined. + */ + if (!CRYPTO_THREAD_run_once(&master_once, init_master_key)) + return NULL; + + /* + * Get our master table sparse array, indexed by key id + */ + mkey = CRYPTO_THREAD_get_local(&master_key); + if (mkey == NULL) + return NULL; + + /* + * Get the specific data entry in the master key + * table for the key id we are searching for + */ + entry = ossl_sa_MASTER_KEY_ENTRY_get(mkey, id); + if (entry == NULL || entry->ctx_table == NULL) + return NULL; + + /* + * If we find an entry above, that will be a second sparse array, + * indexed by OSSL_LIB_CTX. + * Note: Because we're using sparse arrays here, we can do an easy + * trick, since we know all OSSL_LIB_CTX pointers are unique. By casting + * the pointer to a unitptr_t, we can use that as an ordinal index into + * the sparse array. + */ + ctxd = ossl_sa_CTX_TABLE_ENTRY_get(entry->ctx_table, (uintptr_t)ctx); + + /* + * If we find an entry for the passed in context, return its data pointer + */ + return ctxd == NULL ? NULL : ctxd->ctx_data; +} + +/** + * @brief Associates context-specific data with a thread-local key. + * + * This function stores a pointer to data associated with a specific + * thread-local key ID and `OSSL_LIB_CTX` context. It ensures that the + * internal thread-local master key table and all necessary sparse array + * structures are initialized and allocated as needed. + * + * If the key or context-specific entry does not already exist, they will + * be created. This function allows each thread to maintain separate data + * for different library contexts under a shared key identifier. + * + * @param id + * The thread-local key ID to associate the data with. + * + * @param ctx + * Pointer to the `OSSL_LIB_CTX` used as a secondary key for storing + * the data. + * + * @param data + * Pointer to the user-defined context-specific data to store. + * + * @return 1 on success, or 0 if allocation or initialization fails. + */ +int CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id, + OSSL_LIB_CTX *ctx, void *data) +{ + SPARSE_ARRAY_OF(MASTER_KEY_ENTRY) *mkey; + MASTER_KEY_ENTRY *entry; + CTX_TABLE_ENTRY *ctxd; + + /* + * Make sure our master key is initialized + * See notes above on the use of CRYPTO_THREAD_run_once here + */ + if (!CRYPTO_THREAD_run_once(&master_once, init_master_key)) + return 0; + + /* + * Get our local master key data, which will be + * a sparse array indexed by the id parameter + */ + mkey = CRYPTO_THREAD_get_local(&master_key); + if (mkey == NULL) { + /* + * we didn't find one, but that's ok, just initialize it now + */ + mkey = ossl_sa_MASTER_KEY_ENTRY_new(); + if (mkey == NULL) + return 0; + /* + * make sure to assign it to our master key thread-local storage + */ + CRYPTO_THREAD_set_local(&master_key, mkey); + } + + /* + * Find the entry that we are looking for using our id index + */ + entry = ossl_sa_MASTER_KEY_ENTRY_get(mkey, id); + if (entry == NULL) { + + /* + * Didn't find it, that's ok, just add it now + */ + entry = OPENSSL_zalloc(sizeof(MASTER_KEY_ENTRY)); + if (entry == NULL) + return 0; + entry->ctx_table = ossl_sa_CTX_TABLE_ENTRY_new(); + if (entry->ctx_table == NULL) { + OPENSSL_free(entry); + return 0; + } + /* + * Assign it to the appropriate entry in the master key sparse array + */ + ossl_sa_MASTER_KEY_ENTRY_set(mkey, id, entry); + } + + /* + * Now go look up our per context entry, using the OSSL_LIB_CTX pointer + * that we've been provided. Note we cast the pointer to a uintptr_t so + * as to use it as an index in the sparse array + */ + ctxd = ossl_sa_CTX_TABLE_ENTRY_get(entry->ctx_table, (uintptr_t)ctx); + if (ctxd == NULL) { + /* + * No entry for this context, build one + */ + ctxd = OPENSSL_zalloc(sizeof(CTX_TABLE_ENTRY)); + if (ctxd == NULL) + return 0; + /* + * Assign to the entry in the table so that we can find it later + */ + ossl_sa_CTX_TABLE_ENTRY_set(entry->ctx_table, (uintptr_t)ctx, ctxd); + } + /* + * Lastly assign our data pointer + */ + ctxd->ctx_data = data; + return 1; +} + +#ifdef FIPS_MODULE +void CRYPTO_THREAD_clean_local_for_fips(void) +{ + SPARSE_ARRAY_OF(MASTER_KEY_ENTRY) *mkey; + + /* + * If we never initialized the master key, there + * is no data to clean, so we are done here + */ + if (master_key_init == 0) + return; + + mkey = CRYPTO_THREAD_get_local(&master_key); + clean_master_key(mkey); + CRYPTO_THREAD_cleanup_local(&master_key); +} +#endif diff --git a/include/internal/threads_common.h b/include/internal/threads_common.h new file mode 100644 index 00000000000..21785471afc --- /dev/null +++ b/include/internal/threads_common.h @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef _CRYPTO_THREADS_COMMON_H_ +# define _CRYPTO_THREADS_COMMON_H_ + +typedef enum { + CRYPTO_THREAD_LOCAL_RCU_KEY = 0, + CRYPTO_THREAD_LOCAL_DRBG_PRIV_KEY, + CRYPTO_THREAD_LOCAL_DRBG_PUB_KEY, + CRYPTO_THREAD_LOCAL_DEF_CTX_KEY, + CRYPTO_THREAD_LOCAL_ERR_KEY, + CRYPTO_THREAD_LOCAL_ASYNC_CTX_KEY, + CRYPTO_THREAD_LOCAL_ASYNC_POOL_KEY, + CRYPTO_THREAD_LOCAL_TEVENT_KEY, + CRYPTO_THREAD_LOCAL_KEY_MAX +} CRYPTO_THREAD_LOCAL_KEY_ID; + +void *CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id, + OSSL_LIB_CTX *ctx); +int CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id, + OSSL_LIB_CTX *ctx, void *data); + +# ifdef FIPS_MODULE +void CRYPTO_THREAD_clean_local_for_fips(void); +# endif + +#endif diff --git a/providers/fips/self_test.c b/providers/fips/self_test.c index ef7be26ca72..8105343224b 100644 --- a/providers/fips/self_test.c +++ b/providers/fips/self_test.c @@ -18,6 +18,7 @@ #include #include "internal/e_os.h" #include "internal/fips.h" +#include "internal/threads_common.h" #include "internal/tsan_assist.h" #include "prov/providercommon.h" #include "crypto/rand.h" @@ -173,6 +174,7 @@ DEP_INIT_ATTRIBUTE void init(void) DEP_FINI_ATTRIBUTE void cleanup(void) { CRYPTO_THREAD_lock_free(self_test_lock); + CRYPTO_THREAD_clean_local_for_fips(); } #endif