Coverage Report

Created: 2025-10-28 06:56

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