/src/openssl/crypto/threads_common.c
Line | Count | Source (jump to first uncovered line) |
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 | | */ |
172 | | static void clean_master_key_id(MASTER_KEY_ENTRY *entry) |
173 | 0 | { |
174 | 0 | ossl_sa_CTX_TABLE_ENTRY_free(entry->ctx_table); |
175 | 0 | } |
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 | | */ |
190 | | static void clean_master_key(void *data) |
191 | 0 | { |
192 | 0 | MASTER_KEY_ENTRY *mkey = data; |
193 | 0 | int i; |
194 | |
|
195 | 0 | for (i = 0; i < CRYPTO_THREAD_LOCAL_KEY_MAX; i++) { |
196 | 0 | if (mkey[i].ctx_table != NULL) |
197 | 0 | clean_master_key_id(&mkey[i]); |
198 | 0 | } |
199 | 0 | OPENSSL_free(mkey); |
200 | 0 | } |
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 | | */ |
212 | | static void init_master_key(void) |
213 | 3 | { |
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 | 3 | CRYPTO_THREAD_init_local(&master_key, clean_master_key); |
226 | | |
227 | | /* |
228 | | * Indicate that the key has been set up. |
229 | | */ |
230 | 3 | master_key_init = 1; |
231 | 3 | } |
232 | | |
233 | | /** |
234 | | * @brief Retrieves context-specific data from thread-local storage. |
235 | | * |
236 | | * This function looks up and returns the data associated with a given |
237 | | * thread-local key ID and `OSSL_LIB_CTX` context. The data must have |
238 | | * previously been stored using `CRYPTO_THREAD_set_local_ex()`. |
239 | | * |
240 | | * If the master key table is not yet initialized, it will be lazily |
241 | | * initialized via `init_master_key()`. If the requested key or context |
242 | | * entry does not exist, `NULL` is returned. |
243 | | * |
244 | | * @param id |
245 | | * The thread-local key ID used to identify the master key entry. |
246 | | * |
247 | | * @param ctx |
248 | | * Pointer to the `OSSL_LIB_CTX` used to index into the context |
249 | | * table for the specified key. |
250 | | * |
251 | | * @return A pointer to the stored context-specific data, or NULL if no |
252 | | * entry is found or initialization fails. |
253 | | */ |
254 | | void *CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id, OSSL_LIB_CTX *ctx) |
255 | 3.62k | { |
256 | 3.62k | MASTER_KEY_ENTRY *mkey; |
257 | 3.62k | CTX_TABLE_ENTRY ctxd; |
258 | | |
259 | 3.62k | ctx = (ctx == CRYPTO_THREAD_NO_CONTEXT) ? NULL : ossl_lib_ctx_get_concrete(ctx); |
260 | | /* |
261 | | * Make sure the master key has been initialized |
262 | | * NOTE: We use CRYPTO_THREAD_run_once here, rather than the |
263 | | * RUN_ONCE macros. We do this because this code is included both in |
264 | | * libcrypto, and in fips.[dll|dylib|so]. FIPS attempts to avoid doing |
265 | | * one time initialization of global data, and so suppresses the definition |
266 | | * of RUN_ONCE, etc, meaning the build breaks if we were to use that with |
267 | | * fips-enabled. However, this is a special case in which we want/need |
268 | | * this one bit of global data to be initialized in both the fips provider |
269 | | * and in libcrypto, so we use CRYPTO_THREAD_run_one directly, which is |
270 | | * always defined. |
271 | | */ |
272 | 3.62k | if (!CRYPTO_THREAD_run_once(&master_once, init_master_key)) |
273 | 0 | return NULL; |
274 | | |
275 | 3.62k | if (!ossl_assert(id < CRYPTO_THREAD_LOCAL_KEY_MAX)) |
276 | 0 | return NULL; |
277 | | |
278 | | /* |
279 | | * Get our master table sparse array, indexed by key id |
280 | | */ |
281 | 3.62k | mkey = CRYPTO_THREAD_get_local(&master_key); |
282 | 3.62k | if (mkey == NULL) |
283 | 3 | return NULL; |
284 | | |
285 | | /* |
286 | | * Get the specific data entry in the master key |
287 | | * table for the key id we are searching for |
288 | | */ |
289 | 3.62k | if (mkey[id].ctx_table == NULL) |
290 | 0 | return NULL; |
291 | | |
292 | | /* |
293 | | * If we find an entry above, that will be a sparse array, |
294 | | * indexed by OSSL_LIB_CTX. |
295 | | * Note: Because we're using sparse arrays here, we can do an easy |
296 | | * trick, since we know all OSSL_LIB_CTX pointers are unique. By casting |
297 | | * the pointer to a unitptr_t, we can use that as an ordinal index into |
298 | | * the sparse array. |
299 | | */ |
300 | 3.62k | ctxd = ossl_sa_CTX_TABLE_ENTRY_get(mkey[id].ctx_table, (uintptr_t)ctx); |
301 | | |
302 | | /* |
303 | | * If we find an entry for the passed in context, return its data pointer |
304 | | */ |
305 | 3.62k | return ctxd; |
306 | 3.62k | } |
307 | | |
308 | | /** |
309 | | * @brief Associates context-specific data with a thread-local key. |
310 | | * |
311 | | * This function stores a pointer to data associated with a specific |
312 | | * thread-local key ID and `OSSL_LIB_CTX` context. It ensures that the |
313 | | * internal thread-local master key table and all necessary sparse array |
314 | | * structures are initialized and allocated as needed. |
315 | | * |
316 | | * If the key or context-specific entry does not already exist, they will |
317 | | * be created. This function allows each thread to maintain separate data |
318 | | * for different library contexts under a shared key identifier. |
319 | | * |
320 | | * @param id |
321 | | * The thread-local key ID to associate the data with. |
322 | | * |
323 | | * @param ctx |
324 | | * Pointer to the `OSSL_LIB_CTX` used as a secondary key for storing |
325 | | * the data. |
326 | | * |
327 | | * @param data |
328 | | * Pointer to the user-defined context-specific data to store. |
329 | | * |
330 | | * @return 1 on success, or 0 if allocation or initialization fails. |
331 | | */ |
332 | | int CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_KEY_ID id, |
333 | | OSSL_LIB_CTX *ctx, void *data) |
334 | 15 | { |
335 | 15 | MASTER_KEY_ENTRY *mkey; |
336 | | |
337 | 15 | ctx = (ctx == CRYPTO_THREAD_NO_CONTEXT) ? NULL : ossl_lib_ctx_get_concrete(ctx); |
338 | | /* |
339 | | * Make sure our master key is initialized |
340 | | * See notes above on the use of CRYPTO_THREAD_run_once here |
341 | | */ |
342 | 15 | if (!CRYPTO_THREAD_run_once(&master_once, init_master_key)) |
343 | 0 | return 0; |
344 | | |
345 | 15 | if (!ossl_assert(id < CRYPTO_THREAD_LOCAL_KEY_MAX)) |
346 | 0 | return 0; |
347 | | |
348 | | /* |
349 | | * Get our local master key data, which will be |
350 | | * a sparse array indexed by the id parameter |
351 | | */ |
352 | 15 | mkey = CRYPTO_THREAD_get_local(&master_key); |
353 | 15 | if (mkey == NULL) { |
354 | | /* |
355 | | * we didn't find one, but that's ok, just initialize it now |
356 | | */ |
357 | 3 | mkey = OPENSSL_zalloc(sizeof(MASTER_KEY_ENTRY) * CRYPTO_THREAD_LOCAL_KEY_MAX); |
358 | 3 | if (mkey == NULL) |
359 | 0 | return 0; |
360 | | /* |
361 | | * make sure to assign it to our master key thread-local storage |
362 | | */ |
363 | 3 | if (!CRYPTO_THREAD_set_local(&master_key, mkey)) { |
364 | 0 | OPENSSL_free(mkey); |
365 | 0 | return 0; |
366 | 0 | } |
367 | 3 | } |
368 | | |
369 | | /* |
370 | | * Find the entry that we are looking for using our id index |
371 | | */ |
372 | 15 | if (mkey[id].ctx_table == NULL) { |
373 | | |
374 | | /* |
375 | | * Didn't find it, that's ok, just add it now |
376 | | */ |
377 | 3 | mkey[id].ctx_table = ossl_sa_CTX_TABLE_ENTRY_new(); |
378 | 3 | if (mkey[id].ctx_table == NULL) |
379 | 0 | return 0; |
380 | 3 | } |
381 | | |
382 | | /* |
383 | | * Now go look up our per context entry, using the OSSL_LIB_CTX pointer |
384 | | * that we've been provided. Note we cast the pointer to a uintptr_t so |
385 | | * as to use it as an index in the sparse array |
386 | | * |
387 | | * Assign to the entry in the table so that we can find it later |
388 | | */ |
389 | 15 | return ossl_sa_CTX_TABLE_ENTRY_set(mkey[id].ctx_table, |
390 | 15 | (uintptr_t)ctx, data); |
391 | 15 | } |
392 | | |
393 | | #ifdef FIPS_MODULE |
394 | | void CRYPTO_THREAD_clean_local_for_fips(void) |
395 | | { |
396 | | MASTER_KEY_ENTRY *mkey; |
397 | | |
398 | | /* |
399 | | * If we never initialized the master key, there |
400 | | * is no data to clean, so we are done here |
401 | | */ |
402 | | if (master_key_init == 0) |
403 | | return; |
404 | | |
405 | | mkey = CRYPTO_THREAD_get_local(&master_key); |
406 | | if (mkey != NULL) |
407 | | clean_master_key(mkey); |
408 | | CRYPTO_THREAD_cleanup_local(&master_key); |
409 | | } |
410 | | #endif |