]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add new CRYPTO_THREAD_[get|set]_local_ex api
authorNeil Horman <nhorman@openssl.org>
Thu, 12 Jun 2025 17:09:56 +0000 (13:09 -0400)
committerNeil Horman <nhorman@openssl.org>
Fri, 20 Jun 2025 17:01:39 +0000 (13:01 -0400)
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ý <sashan@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/27794)

crypto/build.info
crypto/threads_common.c [new file with mode: 0644]
include/internal/threads_common.h [new file with mode: 0644]
providers/fips/self_test.c

index fd1608263378e3282af836b9f95f5f2c1336669c..e0e481ac76e9644fd6a307608be868576f82f08f 100644 (file)
@@ -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 (file)
index 0000000..27c42a9
--- /dev/null
@@ -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-<thread*ctx> 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 <openssl/crypto.h>
+#include <crypto/cryptlib.h>
+#include <crypto/sparse_array.h>
+#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 (file)
index 0000000..2178547
--- /dev/null
@@ -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
index ef7be26ca722367e4d8484ddf3b03b5b43e04a2c..8105343224b2e03dbe4d8d8ab8e702e017634a6e 100644 (file)
@@ -18,6 +18,7 @@
 #include <openssl/rand.h>
 #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