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