1 /*
2  * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9 
10 /**
11  * @file
12  * @brief Thread-local context-specific data management for OpenSSL
13  *
14  * This file implements a mechanism to store and retrieve context-specific
15  * data using OpenSSL's thread-local storage (TLS) system. It provides a way
16  * to associate and manage data based on a combination of a thread-local key
17  * and an `OSSL_LIB_CTX *` context.
18  *
19  * NOTE: This differs from the CRYPTO_THREAD_[get|set]_local api set in that
20  * this api stores a single OS level thread-local key per-process, and manages
21  * subsequent keys using a series of arrays and sparse arrays stored against
22  * that aforementioned thread local key
23  *
24  * Data Design:
25  *
26  * per-thread master key data  -> +--------------+-+
27  *                                |              | |
28  *                                |              | |
29  *                                +--------------+ |
30  *                                +--------------+ |
31  *                                |              | |
32  *                                |              | |
33  *                                +--------------+ |  fixed  array indexed
34  *                                       .         |      by key id
35  *                                       .         |
36  *                                       .         |
37  *                                +--------------+ |
38  *                                |              | |
39  *                                |       |      | |
40  *                                +-------+------+-+
41  *                                        |
42  *           ++---------------+           |
43  *           ||               |<----------+
44  *           ||               |
45  *           |+---------------+
46  *           |+---------------+
47  *           ||               |
48  *           ||               |  sparse array indexed
49  *           |+---------------+     by libctx pointer
50  *           |        .              cast to uintptr_t
51  *           |        .
52  *           |        .
53  *           |+---------------+
54  *           ||        +------+----> +-----------------+
55  *           ||        |      |      |                 |
56  *           ++--------+------+      +-----------------+
57  *                                  per-<thread*ctx> data
58  *
59  * It uses the following lookup pattern:
60  *   1) A global os defined key to a per-thread fixed array
61  *   2) A libcrypto defined key id as an index to (1) to get a sparse array
62  *   3) A Library context pointer as an index to (2) to produce a per
63  *      thread*context data pointer
64  *
65  * Two primary functions are provided:
66  *   - CRYPTO_THREAD_get_local_ex() retrieves data associated with a key and
67  *     context.
68  *   - CRYPTO_THREAD_set_local_ex() associates data with a given key and
69  *     context, allocating tables as needed.
70  *
71  * Internal structures:
72  *   - CTX_TABLE_ENTRY: wraps a context-specific data pointer.
73  *   - MASTER_KEY_ENTRY: maintains a table of CTX_TABLE_ENTRY and an optional
74  *     cleanup function.
75  *
76  * The implementation ensures:
77  *   - Lazy initialization of master key data using CRYPTO_ONCE.
78  *   - Automatic cleanup of all context and key mappings when a thread exits.
79  *
80  * Cleanup routines:
81  *   - clean_ctx_entry: releases context-specific entries.
82  *   - clean_master_key_id: releases all entries for a specific key ID.
83  *   - clean_master_key: top-level cleanup for the thread-local master key.
84  *
85  */
86 
87 #include <openssl/crypto.h>
88 #include <crypto/cryptlib.h>
89 #include <crypto/sparse_array.h>
90 #include "internal/cryptlib.h"
91 #include "internal/threads_common.h"
92 
93 /**
94  * @struct CTX_TABLE_ENTRY
95  * @brief Represents a wrapper for context-specific data.
96  *
97  * This structure is used to hold a pointer to data that is associated
98  * with a particular `OSSL_LIB_CTX` instance in a thread-local context
99  * mapping. It is stored within a sparse array, allowing efficient
100  * per-context data lookup keyed by a context identifier.
101  *
102  * @var CTX_TABLE_ENTRY::ctx_data
103  * Pointer to the data associated with a given library context.
104  */
105 typedef void *CTX_TABLE_ENTRY;
106 
107 /*
108  * define our sparse array of CTX_TABLE_ENTRY functions
109  */
110 DEFINE_SPARSE_ARRAY_OF(CTX_TABLE_ENTRY);
111 
112 /**
113  * @struct MASTER_KEY_ENTRY
114  * @brief Represents a mapping of context-specific data for a TLS key ID.
115  *
116  * This structure manages a collection of `CTX_TABLE_ENTRY` items, each
117  * associated with a different `OSSL_LIB_CTX` instance. It supports
118  * cleanup of stored data when the thread or key is being destroyed.
119  *
120  * @var MASTER_KEY_ENTRY::ctx_table
121  * Sparse array mapping `OSSL_LIB_CTX` pointers (cast to uintptr_t) to
122  * `CTX_TABLE_ENTRY` structures that hold context-specific data.
123  *
124  */
125 typedef struct master_key_entry {
126     SPARSE_ARRAY_OF(CTX_TABLE_ENTRY) *ctx_table;
127 } MASTER_KEY_ENTRY;
128 
129 /**
130  * @brief holds our per thread data with the operating system
131  *
132  * Global thread local storage pointer, used to create a platform
133  * specific thread-local key
134  */
135 static CRYPTO_THREAD_LOCAL master_key;
136 
137 /**
138  * @brief Informs the library if the master key has been set up
139  *
140  * State variable to track if we have initialized the master_key
141  * If this isn't set to 1, then we need to skip any cleanup
142  * in CRYPTO_THREAD_clean_for_fips, as the uninitialized key
143  * will return garbage data
144  */
145 static uint8_t master_key_init = 0;
146 
147 /**
148  * @brief gate variable to do one time init of the master key
149  *
150  * Run once gate for doing one-time initialization
151  */
152 static CRYPTO_ONCE master_once = CRYPTO_ONCE_STATIC_INIT;
153 
154 /**
155  * @brief Cleans up all context-specific entries for a given key ID.
156  *
157  * This function is used to release all context data associated with a
158  * specific thread-local key (identified by `idx`). It iterates over the
159  * context table in the given `MASTER_KEY_ENTRY`, invoking cleanup for each
160  * `CTX_TABLE_ENTRY`, then frees the context table and the entry itself.
161  *
162  * @param idx
163  *        The key ID associated with the `MASTER_KEY_ENTRY`. Unused.
164  *
165  * @param entry
166  *        Pointer to the `MASTER_KEY_ENTRY` containing the context table
167  *        to be cleaned up.
168  *
169  * @param arg
170  *        Unused parameter.
171  */
clean_master_key_id(MASTER_KEY_ENTRY * entry)172 static void clean_master_key_id(MASTER_KEY_ENTRY *entry)
173 {
174     ossl_sa_CTX_TABLE_ENTRY_free(entry->ctx_table);
175 }
176 
177 /**
178  * @brief Cleans up all master key entries for the current thread.
179  *
180  * This function is the top-level cleanup routine for the thread-local
181  * storage associated with OpenSSL master keys. It is typically registered
182  * as the thread-local storage destructor. It iterates over all
183  * `MASTER_KEY_ENTRY` items in the sparse array, releasing associated
184  * context data and structures.
185  *
186  * @param data
187  *        Pointer to the thread-local `SPARSE_ARRAY_OF(MASTER_KEY_ENTRY)`
188  *        structure to be cleaned up.
189  */
clean_master_key(void * data)190 static void clean_master_key(void *data)
191 {
192     MASTER_KEY_ENTRY *mkey = data;
193     int i;
194 
195     for (i = 0; i < CRYPTO_THREAD_LOCAL_KEY_MAX; i++) {
196         if (mkey[i].ctx_table != NULL)
197             clean_master_key_id(&mkey[i]);
198     }
199     OPENSSL_free(mkey);
200 }
201 
202 /**
203  * @brief Initializes the thread-local storage for master key data.
204  *
205  * This function sets up the thread-local key used to store per-thread
206  * master key tables. It also registers the `clean_master_key` function
207  * as the destructor to be called when the thread exits.
208  *
209  * This function is intended to be called once using `CRYPTO_THREAD_run_once`
210  * to ensure thread-safe initialization.
211  */
init_master_key(void)212 static void init_master_key(void)
213 {
214     /*
215      * Note: We assign a cleanup function here, which is atypical for
216      * uses of CRYPTO_THREAD_init_local.  This is because, nominally
217      * we expect that the use of ossl_init_thread_start will be used
218      * to notify openssl of exiting threads.  However, in this case
219      * we want the metadata for this interface (the sparse arrays) to
220      * stay valid until the thread actually exits, which is what the
221      * clean_master_key function does.  Data held in the sparse arrays
222      * (that is assigned via CRYPTO_THREAD_set_local_ex), are still expected
223      * to be cleaned via the ossl_init_thread_start/stop api.
224      */
225     if (!CRYPTO_THREAD_init_local(&master_key, clean_master_key))
226         return;
227 
228     /*
229      * Indicate that the key has been set up.
230      */
231     master_key_init = 1;
232 }
233 
234 /**
235  * @brief Retrieves context-specific data from thread-local storage.
236  *
237  * This function looks up and returns the data associated with a given
238  * thread-local key ID and `OSSL_LIB_CTX` context. The data must have
239  * previously been stored using `CRYPTO_THREAD_set_local_ex()`.
240  *
241  * If the master key table is not yet initialized, it will be lazily
242  * initialized via `init_master_key()`. If the requested key or context
243  * entry does not exist, `NULL` is returned.
244  *
245  * @param id
246  *        The thread-local key ID used to identify the master key entry.
247  *
248  * @param ctx
249  *        Pointer to the `OSSL_LIB_CTX` used to index into the context
250  *        table for the specified key.
251  *
252  * @return A pointer to the stored context-specific data, or NULL if no
253  *         entry is found or initialization fails.
254  */
CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id,OSSL_LIB_CTX * ctx)255 void *CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id, OSSL_LIB_CTX *ctx)
256 {
257     MASTER_KEY_ENTRY *mkey;
258     CTX_TABLE_ENTRY ctxd;
259 
260     ctx = (ctx == CRYPTO_THREAD_NO_CONTEXT) ? NULL : ossl_lib_ctx_get_concrete(ctx);
261     /*
262      * Make sure the master key has been initialized
263      * NOTE: We use CRYPTO_THREAD_run_once here, rather than the
264      * RUN_ONCE macros.  We do this because this code is included both in
265      * libcrypto, and in fips.[dll|dylib|so].  FIPS attempts to avoid doing
266      * one time initialization of global data, and so suppresses the definition
267      * of RUN_ONCE, etc, meaning the build breaks if we were to use that with
268      * fips-enabled.  However, this is a special case in which we want/need
269      * this one bit of global data to be initialized in both the fips provider
270      * and in libcrypto, so we use CRYPTO_THREAD_run_one directly, which is
271      * always defined.
272      */
273     if (!CRYPTO_THREAD_run_once(&master_once, init_master_key))
274         return NULL;
275 
276     if (!ossl_assert(id < CRYPTO_THREAD_LOCAL_KEY_MAX))
277         return NULL;
278 
279     /*
280      * Get our master table sparse array, indexed by key id
281      */
282     mkey = CRYPTO_THREAD_get_local(&master_key);
283     if (mkey == NULL)
284         return NULL;
285 
286     /*
287      * Get the specific data entry in the master key
288      * table for the key id we are searching for
289      */
290     if (mkey[id].ctx_table == NULL)
291         return NULL;
292 
293     /*
294      * If we find an entry above, that will be a sparse array,
295      * indexed by OSSL_LIB_CTX.
296      * Note: Because we're using sparse arrays here, we can do an easy
297      * trick, since we know all OSSL_LIB_CTX pointers are unique.  By casting
298      * the pointer to a unitptr_t, we can use that as an ordinal index into
299      * the sparse array.
300      */
301     ctxd = ossl_sa_CTX_TABLE_ENTRY_get(mkey[id].ctx_table, (uintptr_t)ctx);
302 
303     /*
304      * If we find an entry for the passed in context, return its data pointer
305      */
306     return ctxd;
307 }
308 
309 /**
310  * @brief Associates context-specific data with a thread-local key.
311  *
312  * This function stores a pointer to data associated with a specific
313  * thread-local key ID and `OSSL_LIB_CTX` context. It ensures that the
314  * internal thread-local master key table and all necessary sparse array
315  * structures are initialized and allocated as needed.
316  *
317  * If the key or context-specific entry does not already exist, they will
318  * be created. This function allows each thread to maintain separate data
319  * for different library contexts under a shared key identifier.
320  *
321  * @param id
322  *        The thread-local key ID to associate the data with.
323  *
324  * @param ctx
325  *        Pointer to the `OSSL_LIB_CTX` used as a secondary key for storing
326  *        the data.
327  *
328  * @param data
329  *        Pointer to the user-defined context-specific data to store.
330  *
331  * @return 1 on success, or 0 if allocation or initialization fails.
332  */
CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id,OSSL_LIB_CTX * ctx,void * data)333 int CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id,
334                                OSSL_LIB_CTX *ctx, void *data)
335 {
336     MASTER_KEY_ENTRY *mkey;
337 
338     ctx = (ctx == CRYPTO_THREAD_NO_CONTEXT) ? NULL : ossl_lib_ctx_get_concrete(ctx);
339     /*
340      * Make sure our master key is initialized
341      * See notes above on the use of CRYPTO_THREAD_run_once here
342      */
343     if (!CRYPTO_THREAD_run_once(&master_once, init_master_key))
344         return 0;
345 
346     if (!ossl_assert(id < CRYPTO_THREAD_LOCAL_KEY_MAX))
347         return 0;
348 
349     /*
350      * Get our local master key data, which will be
351      * a sparse array indexed by the id parameter
352      */
353     mkey = CRYPTO_THREAD_get_local(&master_key);
354     if (mkey == NULL) {
355         /*
356          * we didn't find one, but that's ok, just initialize it now
357          */
358         mkey = OPENSSL_calloc(CRYPTO_THREAD_LOCAL_KEY_MAX,
359                               sizeof(MASTER_KEY_ENTRY));
360         if (mkey == NULL)
361             return 0;
362         /*
363          * make sure to assign it to our master key thread-local storage
364          */
365         if (!CRYPTO_THREAD_set_local(&master_key, mkey)) {
366             OPENSSL_free(mkey);
367             return 0;
368         }
369     }
370 
371     /*
372      * Find the entry that we are looking for using our id index
373      */
374     if (mkey[id].ctx_table == NULL) {
375 
376         /*
377          * Didn't find it, that's ok, just add it now
378          */
379         mkey[id].ctx_table = ossl_sa_CTX_TABLE_ENTRY_new();
380         if (mkey[id].ctx_table == NULL)
381             return 0;
382     }
383 
384     /*
385      * Now go look up our per context entry, using the OSSL_LIB_CTX pointer
386      * that we've been provided.  Note we cast the pointer to a uintptr_t so
387      * as to use it as an index in the sparse array
388      *
389      * Assign to the entry in the table so that we can find it later
390      */
391     return ossl_sa_CTX_TABLE_ENTRY_set(mkey[id].ctx_table,
392                                        (uintptr_t)ctx, data);
393 }
394 
395 #ifdef FIPS_MODULE
CRYPTO_THREAD_clean_local_for_fips(void)396 void CRYPTO_THREAD_clean_local_for_fips(void)
397 {
398     MASTER_KEY_ENTRY *mkey;
399 
400     /*
401      * If we never initialized the master key, there
402      * is no data to clean, so we are done here
403      */
404     if (master_key_init == 0)
405         return;
406 
407     mkey = CRYPTO_THREAD_get_local(&master_key);
408     if (mkey != NULL)
409         clean_master_key(mkey);
410     CRYPTO_THREAD_cleanup_local(&master_key);
411 }
412 #endif
413