Coverage Report

Created: 2026-04-01 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl/crypto/property/property.c
Line
Count
Source
1
/*
2
 * Copyright 2019-2025 The OpenSSL Project Authors. All Rights Reserved.
3
 * Copyright (c) 2019, Oracle and/or its affiliates.  All rights reserved.
4
 *
5
 * Licensed under the Apache License 2.0 (the "License").  You may not use
6
 * this file except in compliance with the License.  You can obtain a copy
7
 * in the file LICENSE in the source distribution or at
8
 * https://www.openssl.org/source/license.html
9
 */
10
11
#include <string.h>
12
#include <stdio.h>
13
#include <stdarg.h>
14
#include <openssl/crypto.h>
15
#include <openssl/provider.h>
16
#include "internal/property.h"
17
#include "internal/provider.h"
18
#include "internal/hashtable.h"
19
#include "internal/tsan_assist.h"
20
#include "internal/list.h"
21
#include "internal/hashfunc.h"
22
#include "internal/time.h"
23
#include <openssl/lhash.h>
24
#include <openssl/rand.h>
25
#include <openssl/trace.h>
26
#include "crypto/sparse_array.h"
27
#include "property_local.h"
28
#include "crypto/context.h"
29
30
/*
31
 * The shard count was determined through performance testing with the evp_fetch
32
 * tool on an Intel Xeon Gold 6248R CPU @ 3.00GHz. Testing showed that 4 shards
33
 * combined with CACHE_SIZE delivered the best performance for 16 or
34
 * more threads, and close to best performance at below 16 threads.
35
 */
36
#ifndef NUM_SHARDS
37
4.82M
#define NUM_SHARDS 4
38
#endif
39
40
#ifndef CACHE_SIZE
41
1.51k
#define CACHE_SIZE 512
42
#endif
43
44
/*
45
 * To keep random cull distributions from being unbiased, we should keep both
46
 * CACHE_SIZE and NUM_SHARDS as powers of 2
47
 */
48
#if (CACHE_SIZE != 0 && (CACHE_SIZE & (CACHE_SIZE - 1)))
49
#error "CACHE_SIZE must be a power of 2"
50
#endif
51
52
#if (NUM_SHARDS != 0 && (NUM_SHARDS & (NUM_SHARDS - 1)))
53
#error "NUM_SHARDS must be a power of 2"
54
#endif
55
/*
56
 * The number of elements in the query cache before we initiate a flush.
57
 * If reducing this, also ensure the stochastic test in test/property_test.c
58
 * isn't likely to fail.
59
 */
60
1.51k
#define IMPL_CACHE_FLUSH_THRESHOLD (CACHE_SIZE / NUM_SHARDS)
61
62
#if defined(__GNUC__) || defined(__clang__)
63
/*
64
 * ALLOW_VLA enables the use of dynamically sized arrays
65
 * in ossl_method_store_cache_[get|set].  This is done for
66
 * performance reasons, as moving the stack pointer is
67
 * way faster than getting memory from heap.  This introduces
68
 * the potential for stack overflows, but we check for that
69
 * by capping the size of the buffer to a large value
70
 * MAX_PROP_QUERY as there shouldn't be any property queries that long.
71
 */
72
#define ALLOW_VLA
73
#endif
74
75
/*
76
 * Max allowed length of our property query
77
 */
78
4.14M
#define MAX_PROP_QUERY 4096
79
80
typedef struct {
81
    void *method;
82
    int (*up_ref)(void *);
83
    void (*free)(void *);
84
} METHOD;
85
86
typedef struct {
87
    const OSSL_PROVIDER *provider;
88
    OSSL_PROPERTY_LIST *properties;
89
    METHOD method;
90
} IMPLEMENTATION;
91
92
DEFINE_STACK_OF(IMPLEMENTATION)
93
94
typedef struct query_st {
95
    OSSL_LIST_MEMBER(lru_entry, struct query_st); /* member of our linked list */
96
    void *saptr; /* pointer to our owning STORED_ALGORITHM */
97
    int nid; /* nid of this query */
98
    uint64_t specific_hash; /* hash of [nid,prop_query,prov] tuple */
99
    uint64_t generic_hash; /* hash of [nid,prop_query] tuple */
100
    METHOD method; /* METHOD for this query */
101
    TSAN_QUALIFIER uint32_t used; /* flag to indicate used since last cull */
102
} QUERY;
103
104
/*
105
 * This is our key to lookup queries
106
 * It has no key data as we allocate and marshall
107
 * it dynamically in ossl_method_store_cache_[set|get]
108
 */
109
typedef struct query_key {
110
    HT_KEY key_header;
111
} QUERY_KEY;
112
113
7.99M
IMPLEMENT_HT_VALUE_TYPE_FNS(QUERY, cache, static)
property.c:ossl_ht_cache_QUERY_from_value
Line
Count
Source
113
IMPLEMENT_HT_VALUE_TYPE_FNS(QUERY, cache, static)
property.c:ossl_ht_cache_QUERY_get
Line
Count
Source
113
IMPLEMENT_HT_VALUE_TYPE_FNS(QUERY, cache, static)
property.c:ossl_ht_cache_QUERY_insert
Line
Count
Source
113
IMPLEMENT_HT_VALUE_TYPE_FNS(QUERY, cache, static)
114
115
1.03M
DEFINE_LIST_OF(lru_entry, QUERY);
property.c:ossl_list_lru_entry_head
Line
Count
Source
115
DEFINE_LIST_OF(lru_entry, QUERY);
property.c:ossl_list_lru_entry_next
Line
Count
Source
115
DEFINE_LIST_OF(lru_entry, QUERY);
property.c:ossl_list_lru_entry_remove
Line
Count
Source
115
DEFINE_LIST_OF(lru_entry, QUERY);
property.c:ossl_list_lru_entry_insert_tail
Line
Count
Source
115
DEFINE_LIST_OF(lru_entry, QUERY);
property.c:ossl_list_lru_entry_insert_head
Line
Count
Source
115
DEFINE_LIST_OF(lru_entry, QUERY);
116
1.03M
117
1.03M
typedef OSSL_LIST(lru_entry) QUERY_LRU_LIST;
118
1.03M
119
1.03M
typedef struct {
120
1.03M
    int nid;
121
1.03M
    STACK_OF(IMPLEMENTATION) *impls;
122
1.03M
} ALGORITHM;
123
1.03M
124
1.03M
typedef struct {
125
1.03M
    SPARSE_ARRAY_OF(ALGORITHM) * algs;
126
1.03M
127
1.03M
    HT *cache;
128
1.03M
    /*
129
1.03M
     * This is a list of every element in our query
130
1.03M
     * cache.  NOTE: Its named lru list, but to avoid
131
1.03M
     * having to remove/insert to the list a bunch, it
132
1.03M
     * actually just uses a heuristic with the QUERY used
133
1.03M
     * flag to identify recently used QUERY elements
134
1.03M
     */
135
1.03M
    QUERY_LRU_LIST lru_list;
136
1.03M
    /*
137
1.03M
     * Lock to protect each shard of |algs| from concurrent writing,
138
1.03M
     * when individual implementations or queries are inserted.  This is used
139
1.03M
     * by the appropriate functions here.
140
1.03M
     */
141
1.03M
    CRYPTO_RWLOCK *lock;
142
1.03M
143
1.03M
    /* query cache specific values */
144
1.03M
145
1.03M
} STORED_ALGORITHMS;
146
1.03M
147
1.03M
struct ossl_method_store_st {
148
1.03M
    OSSL_LIB_CTX *ctx;
149
1.03M
    STORED_ALGORITHMS *algs;
150
1.03M
    /*
151
1.03M
     * Lock to reserve the whole store.  This is used when fetching a set
152
1.03M
     * of algorithms, via these functions, found in crypto/core_fetch.c:
153
1.03M
     * ossl_method_construct_reserve_store()
154
1.03M
     * ossl_method_construct_unreserve_store()
155
1.03M
     */
156
1.03M
    CRYPTO_RWLOCK *biglock;
157
1.03M
};
158
1.03M
159
1.03M
DEFINE_SPARSE_ARRAY_OF(ALGORITHM);
160
1.03M
161
1.03M
DEFINE_STACK_OF(ALGORITHM)
162
1.03M
163
1.03M
typedef struct ossl_global_properties_st {
164
1.03M
    OSSL_PROPERTY_LIST *list;
165
1.03M
#ifndef FIPS_MODULE
166
1.03M
    unsigned int no_mirrored : 1;
167
1.03M
#endif
168
1.03M
} OSSL_GLOBAL_PROPERTIES;
169
1.03M
170
4.62M
#define stored_algs_shard(store, nid) (&(store)->algs[(nid) & (NUM_SHARDS - 1)])
171
172
static void ossl_method_cache_flush_alg(STORED_ALGORITHMS *sa,
173
    ALGORITHM *alg);
174
static void ossl_method_cache_flush(STORED_ALGORITHMS *sa, int nid);
175
176
/* Global properties are stored per library context */
177
void ossl_ctx_global_properties_free(void *vglobp)
178
196
{
179
196
    OSSL_GLOBAL_PROPERTIES *globp = vglobp;
180
181
196
    if (globp != NULL) {
182
196
        ossl_property_free(globp->list);
183
196
        OPENSSL_free(globp);
184
196
    }
185
196
}
186
187
void *ossl_ctx_global_properties_new(OSSL_LIB_CTX *ctx)
188
380
{
189
380
    return OPENSSL_zalloc(sizeof(OSSL_GLOBAL_PROPERTIES));
190
380
}
191
192
OSSL_PROPERTY_LIST **ossl_ctx_global_properties(OSSL_LIB_CTX *libctx,
193
    ossl_unused int loadconfig)
194
21.5k
{
195
21.5k
    OSSL_GLOBAL_PROPERTIES *globp;
196
197
21.5k
#if !defined(FIPS_MODULE) && !defined(OPENSSL_NO_AUTOLOAD_CONFIG)
198
21.5k
    if (loadconfig && !OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL))
199
0
        return NULL;
200
21.5k
#endif
201
21.5k
    globp = ossl_lib_ctx_get_data(libctx, OSSL_LIB_CTX_GLOBAL_PROPERTIES);
202
203
21.5k
    return globp != NULL ? &globp->list : NULL;
204
21.5k
}
205
206
#ifndef FIPS_MODULE
207
int ossl_global_properties_no_mirrored(OSSL_LIB_CTX *libctx)
208
0
{
209
0
    OSSL_GLOBAL_PROPERTIES *globp
210
0
        = ossl_lib_ctx_get_data(libctx, OSSL_LIB_CTX_GLOBAL_PROPERTIES);
211
212
0
    return globp != NULL && globp->no_mirrored ? 1 : 0;
213
0
}
214
215
void ossl_global_properties_stop_mirroring(OSSL_LIB_CTX *libctx)
216
0
{
217
0
    OSSL_GLOBAL_PROPERTIES *globp
218
0
        = ossl_lib_ctx_get_data(libctx, OSSL_LIB_CTX_GLOBAL_PROPERTIES);
219
220
0
    if (globp != NULL)
221
0
        globp->no_mirrored = 1;
222
0
}
223
#endif
224
225
static int ossl_method_up_ref(METHOD *method)
226
24.5M
{
227
24.5M
    return (*method->up_ref)(method->method);
228
24.5M
}
229
230
static void ossl_method_free(METHOD *method)
231
24.3k
{
232
24.3k
    (*method->free)(method->method);
233
24.3k
}
234
235
static __owur int ossl_property_read_lock(STORED_ALGORITHMS *p)
236
27.2M
{
237
27.2M
    return p != NULL ? CRYPTO_THREAD_read_lock(p->lock) : 0;
238
27.2M
}
239
240
static __owur int ossl_property_write_lock(STORED_ALGORITHMS *p)
241
34.1k
{
242
34.1k
    return p != NULL ? CRYPTO_THREAD_write_lock(p->lock) : 0;
243
34.1k
}
244
245
static int ossl_property_unlock(STORED_ALGORITHMS *p)
246
27.3M
{
247
27.3M
    return p != 0 ? CRYPTO_THREAD_unlock(p->lock) : 0;
248
27.3M
}
249
250
static void impl_free(IMPLEMENTATION *impl)
251
17.2k
{
252
17.2k
    if (impl != NULL) {
253
17.2k
        ossl_method_free(&impl->method);
254
17.2k
        OPENSSL_free(impl);
255
17.2k
    }
256
17.2k
}
257
258
static ossl_inline void impl_cache_free(QUERY *elem)
259
1.50k
{
260
1.50k
    if (elem != NULL) {
261
1.50k
        STORED_ALGORITHMS *sa = elem->saptr;
262
263
1.50k
#ifndef NDEBUG
264
1.50k
        if (elem->ossl_list_lru_entry.list != NULL)
265
1.49k
            ossl_list_lru_entry_remove(&sa->lru_list, elem);
266
#else
267
        ossl_list_lru_entry_remove(&sa->lru_list, elem);
268
#endif
269
1.50k
        ossl_method_free(&elem->method);
270
1.50k
        OPENSSL_free(elem);
271
1.50k
    }
272
1.50k
}
273
274
/*
275
 * This is the registered free function for all of our
276
 * allocated QUERY hashtables
277
 */
278
static void query_free(HT_VALUE *v)
279
1.49k
{
280
1.49k
    QUERY *elem = ossl_ht_cache_QUERY_from_value(v);
281
1.49k
    impl_cache_free(elem);
282
1.49k
}
283
284
static void impl_cache_flush_alg(ALGORITHM *alg, STORED_ALGORITHMS *sa)
285
1.18k
{
286
1.18k
    QUERY *q, *qn;
287
1.18k
    uint64_t hash;
288
1.18k
    QUERY_KEY key;
289
290
    /*
291
     * Instead of iterating over the hashtable with the
292
     * ossl_ht_foreach_until function, we just traverse the
293
     * linked list, as it much faster this way, as we avoid having
294
     * to visit lots of potentially empty nodes
295
     */
296
1.18k
    OSSL_LIST_FOREACH_DELSAFE(q, qn, lru_entry, &sa->lru_list)
297
0
    {
298
        /*
299
         * Check for a match by nid, as we're only deleting QUERY elements
300
         * that are for the nid specified in alg
301
         */
302
0
        if (q->nid == alg->nid) {
303
            /*
304
             * We can accelerate hash table operations here, by creating a key
305
             * with a cached hash value, to avoid having to compute it again
306
             * NOTE: Each QUERY contains 2 possible hash values, that we use in
307
             * a priority order.  Every QUERY has a generic_hash, which is the computed
308
             * hash of the [nid, prop_query] tuple, and may have a specific_hash,
309
             * which is the computed has of the [nid, prop_query, provider] tuple.
310
             * We use the specific hash if its available, otherwise use the
311
             * generic_hash
312
             */
313
0
            hash = (q->specific_hash != 0) ? q->specific_hash : q->generic_hash;
314
0
            HT_INIT_KEY_CACHED(&key, hash);
315
0
            ossl_ht_delete(sa->cache, TO_HT_KEY(&key));
316
0
        }
317
0
    }
318
1.18k
}
319
320
static void alg_cleanup(ossl_uintmax_t idx, ALGORITHM *a, void *arg)
321
13.3k
{
322
13.3k
    STORED_ALGORITHMS *sa = arg;
323
324
13.3k
    if (a != NULL) {
325
13.3k
        sk_IMPLEMENTATION_pop_free(a->impls, &impl_free);
326
13.3k
        OPENSSL_free(a);
327
13.3k
    }
328
13.3k
    if (sa != NULL)
329
13.3k
        ossl_sa_ALGORITHM_set(sa->algs, idx, NULL);
330
13.3k
}
331
332
static void stored_algs_free(STORED_ALGORITHMS *sa)
333
0
{
334
0
    if (sa == NULL)
335
0
        return;
336
337
0
    for (int i = 0; i < NUM_SHARDS; ++i) {
338
0
        ossl_sa_ALGORITHM_doall_arg(sa[i].algs, &alg_cleanup, &sa[i]);
339
0
        ossl_sa_ALGORITHM_free(sa[i].algs);
340
0
        CRYPTO_THREAD_lock_free(sa[i].lock);
341
0
        ossl_ht_free(sa[i].cache);
342
0
    }
343
344
0
    OPENSSL_free(sa);
345
0
}
346
347
static STORED_ALGORITHMS *stored_algs_new(OSSL_LIB_CTX *ctx)
348
312
{
349
312
    STORED_ALGORITHMS *ret;
350
312
    HT_CONFIG ht_conf = {
351
312
        .ctx = ctx,
352
312
        .ht_free_fn = query_free,
353
312
        .ht_hash_fn = NULL,
354
312
        .init_neighborhoods = 1,
355
312
        .collision_check = 1,
356
312
        .lockless_reads = 0,
357
312
        .no_rcu = 1
358
312
    };
359
360
312
    ret = OPENSSL_calloc(NUM_SHARDS, sizeof(STORED_ALGORITHMS));
361
312
    if (ret == NULL)
362
0
        return NULL;
363
364
1.56k
    for (int i = 0; i < NUM_SHARDS; ++i) {
365
1.24k
        ret[i].algs = ossl_sa_ALGORITHM_new();
366
1.24k
        if (ret[i].algs == NULL)
367
0
            goto err;
368
369
1.24k
        ret[i].lock = CRYPTO_THREAD_lock_new();
370
1.24k
        if (ret[i].lock == NULL)
371
0
            goto err;
372
1.24k
        ret[i].cache = ossl_ht_new(&ht_conf);
373
1.24k
        if (ret[i].cache == NULL)
374
0
            goto err;
375
1.24k
    }
376
377
312
    return ret;
378
379
0
err:
380
0
    stored_algs_free(ret);
381
382
0
    return NULL;
383
312
}
384
385
/*
386
 * The OSSL_LIB_CTX param here allows access to underlying property data needed
387
 * for computation
388
 */
389
OSSL_METHOD_STORE *ossl_method_store_new(OSSL_LIB_CTX *ctx)
390
312
{
391
312
    OSSL_METHOD_STORE *res;
392
393
312
    res = OPENSSL_zalloc(sizeof(*res));
394
312
    if (res != NULL) {
395
312
        res->ctx = ctx;
396
312
        if ((res->algs = stored_algs_new(ctx)) == NULL
397
312
            || (res->biglock = CRYPTO_THREAD_lock_new()) == NULL) {
398
0
            ossl_method_store_free(res);
399
0
            return NULL;
400
0
        }
401
312
    }
402
312
    return res;
403
312
}
404
405
void ossl_method_store_free(OSSL_METHOD_STORE *store)
406
{
407
    if (store == NULL)
408
        return;
409
410
    stored_algs_free(store->algs);
411
    CRYPTO_THREAD_lock_free(store->biglock);
412
    OPENSSL_free(store);
413
}
414
415
int ossl_method_lock_store(OSSL_METHOD_STORE *store)
416
7.06M
{
417
7.06M
    return store != NULL ? CRYPTO_THREAD_write_lock(store->biglock) : 0;
418
7.06M
}
419
420
int ossl_method_unlock_store(OSSL_METHOD_STORE *store)
421
7.06M
{
422
7.06M
    return store != NULL ? CRYPTO_THREAD_unlock(store->biglock) : 0;
423
7.06M
}
424
425
static ALGORITHM *ossl_method_store_retrieve(STORED_ALGORITHMS *sa, int nid)
426
26.2M
{
427
26.2M
    return ossl_sa_ALGORITHM_get(sa->algs, nid);
428
26.2M
}
429
430
static int ossl_method_store_insert(STORED_ALGORITHMS *sa, ALGORITHM *alg)
431
20.0k
{
432
20.0k
    return ossl_sa_ALGORITHM_set(sa->algs, alg->nid, alg);
433
20.0k
}
434
435
/**
436
 * @brief Adds a method to the specified method store.
437
 *
438
 * This function adds a new method to the provided method store, associating it
439
 * with a specified id, properties, and provider. The method is stored with
440
 * reference count and destruction callbacks.
441
 *
442
 * @param store Pointer to the OSSL_METHOD_STORE where the method will be added.
443
 *              Must be non-null.
444
 * @param prov Pointer to the OSSL_PROVIDER for the provider of the method.
445
 *             Must be non-null.
446
 * @param nid (identifier) associated with the method, must be > 0
447
 * @param properties String containing properties of the method.
448
 * @param method Pointer to the method to be added.
449
 * @param method_up_ref Function pointer for incrementing the method ref count.
450
 * @param method_destruct Function pointer for destroying the method.
451
 *
452
 * @return 1 if the method is successfully added, 0 on failure.
453
 *
454
 * If tracing is enabled, a message is printed indicating that the method is
455
 * being added to the method store.
456
 *
457
 * NOTE: The nid parameter here is _not_ a nid in the sense of the NID_* macros.
458
 * It is an internal unique identifier.
459
 */
460
int ossl_method_store_add(OSSL_METHOD_STORE *store, const OSSL_PROVIDER *prov,
461
    int nid, const char *properties, void *method,
462
    int (*method_up_ref)(void *),
463
    void (*method_destruct)(void *))
464
6.11k
{
465
6.11k
    STORED_ALGORITHMS *sa;
466
6.11k
    ALGORITHM *alg = NULL;
467
6.11k
    IMPLEMENTATION *impl;
468
6.11k
    int ret = 0;
469
6.11k
    int i;
470
471
6.11k
    if (nid <= 0 || method == NULL || store == NULL)
472
0
        return 0;
473
474
6.11k
    if (properties == NULL)
475
0
        properties = "";
476
477
6.11k
    if (!ossl_assert(prov != NULL))
478
0
        return 0;
479
480
    /* Create new entry */
481
6.11k
    impl = OPENSSL_malloc(sizeof(*impl));
482
6.11k
    if (impl == NULL)
483
0
        return 0;
484
6.11k
    impl->method.method = method;
485
6.11k
    impl->method.up_ref = method_up_ref;
486
6.11k
    impl->method.free = method_destruct;
487
6.11k
    if (!ossl_method_up_ref(&impl->method)) {
488
0
        OPENSSL_free(impl);
489
0
        return 0;
490
0
    }
491
6.11k
    impl->provider = prov;
492
493
6.11k
    sa = stored_algs_shard(store, nid);
494
495
    /* Insert into the hash table if required */
496
6.11k
    if (!ossl_property_write_lock(sa)) {
497
0
        impl_free(impl);
498
0
        return 0;
499
0
    }
500
501
    /*
502
     * Flush the alg cache of any implementation that already exists
503
     * for this id.
504
     * This is done to ensure that on the next lookup we go through the
505
     * provider comparison in ossl_method_store_fetch.  If we don't do this
506
     * then this new method won't be given a chance to get selected.
507
     * NOTE: This doesn't actually remove the method from the backing store
508
     * It just ensures that we query the backing store when (re)-adding a
509
     * method to the algorithm cache, in case the one selected by the next
510
     * query selects a different implementation
511
     */
512
6.11k
    ossl_method_cache_flush(sa, nid);
513
514
    /*
515
     * Parse the properties associated with this method, and convert it to a
516
     * property list stored against the implementation for later comparison
517
     * during fetch operations
518
     */
519
6.11k
    if ((impl->properties = ossl_prop_defn_get(store->ctx, properties)) == NULL) {
520
333
        impl->properties = ossl_parse_property(store->ctx, properties);
521
333
        if (impl->properties == NULL)
522
0
            goto err;
523
333
        if (!ossl_prop_defn_set(store->ctx, properties, &impl->properties)) {
524
0
            ossl_property_free(impl->properties);
525
0
            impl->properties = NULL;
526
0
            goto err;
527
0
        }
528
333
    }
529
530
    /*
531
     * Check if we have an algorithm cache already for this nid.  If so use
532
     * it, otherwise, create it, and insert it into the store
533
     */
534
6.11k
    alg = ossl_method_store_retrieve(sa, nid);
535
6.11k
    if (alg == NULL) {
536
4.93k
        if ((alg = OPENSSL_zalloc(sizeof(*alg))) == NULL
537
4.93k
            || (alg->impls = sk_IMPLEMENTATION_new_null()) == NULL)
538
0
            goto err;
539
4.93k
        alg->nid = nid;
540
4.93k
        if (!ossl_method_store_insert(sa, alg))
541
0
            goto err;
542
4.93k
        OSSL_TRACE2(QUERY, "Inserted an alg with nid %d into the stored algorithms %p\n",
543
4.93k
            nid, (void *)sa);
544
4.93k
    }
545
546
    /* Push onto stack if there isn't one there already */
547
9.96k
    for (i = 0; i < sk_IMPLEMENTATION_num(alg->impls); i++) {
548
3.84k
        const IMPLEMENTATION *tmpimpl = sk_IMPLEMENTATION_value(alg->impls, i);
549
550
3.84k
        if (tmpimpl->provider == impl->provider
551
3.84k
            && tmpimpl->properties == impl->properties)
552
0
            break;
553
3.84k
    }
554
555
6.11k
    if (i == sk_IMPLEMENTATION_num(alg->impls)
556
6.11k
        && sk_IMPLEMENTATION_push(alg->impls, impl)) {
557
6.11k
        ret = 1;
558
6.11k
#ifndef FIPS_MODULE
559
6.11k
        OSSL_TRACE_BEGIN(QUERY)
560
0
        {
561
0
            BIO_printf(trc_out, "Adding to method store "
562
0
                                "nid: %d\nproperties: %s\nprovider: %s\n",
563
0
                nid, properties,
564
0
                ossl_provider_name(prov) == NULL ? "none" : ossl_provider_name(prov));
565
0
        }
566
6.11k
        OSSL_TRACE_END(QUERY);
567
6.11k
#endif
568
6.11k
    }
569
6.11k
    ossl_property_unlock(sa);
570
6.11k
    if (ret == 0)
571
0
        impl_free(impl);
572
6.11k
    return ret;
573
574
0
err:
575
0
    ossl_property_unlock(sa);
576
0
    alg_cleanup(0, alg, NULL);
577
0
    impl_free(impl);
578
0
    return 0;
579
6.11k
}
580
581
int ossl_method_store_remove(OSSL_METHOD_STORE *store, int nid,
582
    const void *method)
583
0
{
584
0
    ALGORITHM *alg = NULL;
585
0
    STORED_ALGORITHMS *sa;
586
0
    int i;
587
588
0
    if (nid <= 0 || method == NULL || store == NULL)
589
0
        return 0;
590
591
0
    sa = stored_algs_shard(store, nid);
592
0
    if (!ossl_property_write_lock(sa))
593
0
        return 0;
594
0
    ossl_method_cache_flush(sa, nid);
595
0
    alg = ossl_method_store_retrieve(sa, nid);
596
0
    if (alg == NULL) {
597
0
        ossl_property_unlock(sa);
598
0
        return 0;
599
0
    }
600
601
    /*
602
     * A sorting find then a delete could be faster but these stacks should be
603
     * relatively small, so we avoid the overhead.  Sorting could also surprise
604
     * users when result orderings change (even though they are not guaranteed).
605
     */
606
0
    for (i = 0; i < sk_IMPLEMENTATION_num(alg->impls); i++) {
607
0
        IMPLEMENTATION *impl = sk_IMPLEMENTATION_value(alg->impls, i);
608
609
0
        if (impl->method.method == method) {
610
0
            impl_free(impl);
611
0
            (void)sk_IMPLEMENTATION_delete(alg->impls, i);
612
0
            ossl_property_unlock(sa);
613
0
            return 1;
614
0
        }
615
0
    }
616
0
    ossl_property_unlock(sa);
617
0
    return 0;
618
0
}
619
620
struct alg_cleanup_by_provider_data_st {
621
    STORED_ALGORITHMS *sa;
622
    const OSSL_PROVIDER *prov;
623
};
624
625
/**
626
 * @brief Cleans up implementations of an algorithm associated with a provider.
627
 *
628
 * This function removes all implementations of a specified algorithm that are
629
 * associated with a given provider. The function walks through the stack of
630
 * implementations backwards to handle deletions without affecting indexing.
631
 *
632
 * @param idx Index of the algorithm (unused in this function).
633
 * @param alg Pointer to the ALGORITHM structure containing the implementations.
634
 * @param arg Pointer to the data containing the provider information.
635
 *
636
 * If tracing is enabled, messages are printed indicating the removal of each
637
 * implementation and its properties. If any implementation is removed, the
638
 * associated cache is flushed.
639
 */
640
static void
641
alg_cleanup_by_provider(ossl_uintmax_t idx, ALGORITHM *alg, void *arg)
642
0
{
643
0
    struct alg_cleanup_by_provider_data_st *data = arg;
644
0
    int i, count;
645
646
    /*
647
     * We walk the stack backwards, to avoid having to deal with stack shifts
648
     * caused by deletion
649
     */
650
0
    for (count = 0, i = sk_IMPLEMENTATION_num(alg->impls); i-- > 0;) {
651
0
        IMPLEMENTATION *impl = sk_IMPLEMENTATION_value(alg->impls, i);
652
653
0
        if (impl->provider == data->prov) {
654
0
#ifndef FIPS_MODULE
655
0
            OSSL_TRACE_BEGIN(QUERY)
656
0
            {
657
0
                char buf[512];
658
0
                size_t size;
659
660
0
                size = ossl_property_list_to_string(NULL, impl->properties, buf,
661
0
                    sizeof(buf));
662
0
                BIO_printf(trc_out, "Removing implementation from "
663
0
                                    "query cache\nproperties %s\nprovider %s\n",
664
0
                    size == 0 ? "none" : buf,
665
0
                    ossl_provider_name(impl->provider) == NULL ? "none" : ossl_provider_name(impl->provider));
666
0
            }
667
0
            OSSL_TRACE_END(QUERY);
668
0
#endif
669
670
0
            (void)sk_IMPLEMENTATION_delete(alg->impls, i);
671
0
            count++;
672
0
            impl_free(impl);
673
0
        }
674
0
    }
675
676
    /*
677
     * If we removed any implementation, we also clear the whole associated
678
     * cache, 'cause that's the sensible thing to do.
679
     * There's no point flushing the cache entries where we didn't remove
680
     * any implementation, though.
681
     */
682
0
    if (count > 0)
683
0
        ossl_method_cache_flush_alg(data->sa, alg);
684
0
}
685
686
int ossl_method_store_remove_all_provided(OSSL_METHOD_STORE *store,
687
    const OSSL_PROVIDER *prov)
688
0
{
689
0
    struct alg_cleanup_by_provider_data_st data;
690
691
0
    for (int k = 0; k < NUM_SHARDS; ++k) {
692
0
        STORED_ALGORITHMS *sa = &store->algs[k];
693
694
0
        if (!ossl_property_write_lock(sa))
695
0
            return 0;
696
0
        data.prov = prov;
697
0
        data.sa = sa;
698
0
        ossl_sa_ALGORITHM_doall_arg(sa->algs, &alg_cleanup_by_provider, &data);
699
0
        ossl_property_unlock(sa);
700
0
    }
701
0
    return 1;
702
0
}
703
704
static void alg_do_one(ALGORITHM *alg, IMPLEMENTATION *impl,
705
    void (*fn)(int id, void *method, void *fnarg),
706
    void *fnarg)
707
80.8M
{
708
80.8M
    fn(alg->nid, impl->method.method, fnarg);
709
80.8M
}
710
711
static void alg_copy(ossl_uintmax_t idx, ALGORITHM *alg, void *arg)
712
36.9M
{
713
36.9M
    STACK_OF(ALGORITHM) *newalg = arg;
714
715
36.9M
    alg = OPENSSL_memdup(alg, sizeof(ALGORITHM));
716
36.9M
    if (alg == NULL)
717
0
        return;
718
719
36.9M
    alg->impls = sk_IMPLEMENTATION_dup(alg->impls);
720
721
36.9M
    (void)sk_ALGORITHM_push(newalg, alg);
722
36.9M
}
723
724
static void del_tmpalg(ALGORITHM *alg)
725
36.9M
{
726
36.9M
    sk_IMPLEMENTATION_free(alg->impls);
727
36.9M
    OPENSSL_free(alg);
728
36.9M
}
729
730
void ossl_method_store_do_all(OSSL_METHOD_STORE *store,
731
    void (*fn)(int id, void *method, void *fnarg),
732
    void *fnarg)
733
39.8k
{
734
39.8k
    int i, j;
735
39.8k
    int numalgs, numimps;
736
39.8k
    STACK_OF(ALGORITHM) *tmpalgs;
737
39.8k
    ALGORITHM *alg;
738
739
39.8k
    if (store == NULL)
740
0
        return;
741
742
199k
    for (int k = 0; k < NUM_SHARDS; ++k) {
743
159k
        STORED_ALGORITHMS *sa = &store->algs[k];
744
745
159k
        if (!ossl_property_read_lock(sa))
746
0
            return;
747
748
159k
        tmpalgs = sk_ALGORITHM_new_reserve(NULL,
749
159k
            (int)ossl_sa_ALGORITHM_num(sa->algs));
750
159k
        if (tmpalgs == NULL) {
751
0
            ossl_property_unlock(sa);
752
0
            return;
753
0
        }
754
755
159k
        ossl_sa_ALGORITHM_doall_arg(sa->algs, alg_copy, tmpalgs);
756
159k
        ossl_property_unlock(sa);
757
159k
        numalgs = sk_ALGORITHM_num(tmpalgs);
758
1.94M
        for (i = 0; i < numalgs; i++) {
759
1.78M
            alg = sk_ALGORITHM_value(tmpalgs, i);
760
1.78M
            numimps = sk_IMPLEMENTATION_num(alg->impls);
761
10.3M
            for (j = 0; j < numimps; j++)
762
8.56M
                alg_do_one(alg, sk_IMPLEMENTATION_value(alg->impls, j), fn, fnarg);
763
1.78M
        }
764
159k
        sk_ALGORITHM_pop_free(tmpalgs, del_tmpalg);
765
159k
    }
766
39.8k
}
767
768
/**
769
 * @brief Fetches a method from the method store matching the given properties.
770
 *
771
 * This function searches the method store for an implementation of a specified
772
 * method, identified by its id (nid), and matching the given property query. If
773
 * successful, it returns the method and its associated provider.
774
 *
775
 * @param store Pointer to the OSSL_METHOD_STORE from which to fetch the method.
776
 *              Must be non-null.
777
 * @param nid (identifier) of the method to be fetched. Must be > 0
778
 * @param prop_query String containing the property query to match against.
779
 * @param prov_rw Pointer to the OSSL_PROVIDER to restrict the search to, or
780
 *                to receive the matched provider.
781
 * @param method Pointer to receive the fetched method. Must be non-null.
782
 *
783
 * @return 1 if the method is successfully fetched, 0 on failure.
784
 *
785
 * If tracing is enabled, a message is printed indicating the property query and
786
 * the resolved provider.
787
 *
788
 * NOTE: The nid parameter here is _not_ a NID in the sense of the NID_* macros.
789
 * It is a unique internal identifier value.
790
 */
791
int ossl_method_store_fetch(OSSL_METHOD_STORE *store,
792
    int nid, const char *prop_query,
793
    const OSSL_PROVIDER **prov_rw, void **method)
794
472k
{
795
472k
    OSSL_PROPERTY_LIST **plp;
796
472k
    ALGORITHM *alg;
797
472k
    IMPLEMENTATION *impl, *best_impl = NULL;
798
472k
    OSSL_PROPERTY_LIST *pq = NULL, *p2 = NULL;
799
472k
    const OSSL_PROVIDER *prov = prov_rw != NULL ? *prov_rw : NULL;
800
472k
    int ret = 0;
801
472k
    int j, best = -1, score, optional;
802
472k
    STORED_ALGORITHMS *sa;
803
804
472k
    if (nid <= 0 || method == NULL || store == NULL)
805
0
        return 0;
806
807
472k
#if !defined(FIPS_MODULE) && !defined(OPENSSL_NO_AUTOLOAD_CONFIG)
808
472k
    if (ossl_lib_ctx_is_default(store->ctx)
809
467k
        && !OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL))
810
0
        return 0;
811
472k
#endif
812
813
472k
    sa = stored_algs_shard(store, nid);
814
815
    /* This only needs to be a read lock, because the query won't create anything */
816
472k
    if (!ossl_property_read_lock(sa))
817
0
        return 0;
818
819
472k
    OSSL_TRACE2(QUERY, "Retrieving by nid %d from stored algorithms %p\n",
820
472k
        nid, (void *)sa);
821
472k
    alg = ossl_method_store_retrieve(sa, nid);
822
472k
    if (alg == NULL) {
823
466k
        ossl_property_unlock(sa);
824
466k
        OSSL_TRACE2(QUERY, "Failed to retrieve by nid %d from stored algorithms %p\n",
825
466k
            nid, (void *)sa);
826
466k
        return 0;
827
466k
    }
828
6.02k
    OSSL_TRACE2(QUERY, "Retrieved by nid %d from stored algorithms %p\n",
829
6.02k
        nid, (void *)sa);
830
831
    /*
832
     * If a property query string is provided, convert it to an
833
     * OSSL_PROPERTY_LIST structure
834
     */
835
6.02k
    if (prop_query != NULL)
836
6.02k
        p2 = pq = ossl_parse_query(store->ctx, prop_query, 0);
837
838
    /*
839
     * If the library context has default properties specified
840
     * then merge those with the properties passed to this function
841
     */
842
6.02k
    plp = ossl_ctx_global_properties(store->ctx, 0);
843
6.02k
    if (plp != NULL && *plp != NULL) {
844
0
        if (pq == NULL) {
845
0
            pq = *plp;
846
0
        } else {
847
0
            p2 = ossl_property_merge(pq, *plp);
848
0
            ossl_property_free(pq);
849
0
            if (p2 == NULL)
850
0
                goto fin;
851
0
            pq = p2;
852
0
        }
853
0
    }
854
855
    /*
856
     * Search for a provider that provides this implementation.
857
     * If the requested provider is NULL, then any provider will do,
858
     * otherwise we should try to find the one that matches the requested
859
     * provider.  Note that providers are given implicit preference via the
860
     * ordering of the implementation stack
861
     */
862
6.02k
    if (pq == NULL) {
863
3.69k
        for (j = 0; j < sk_IMPLEMENTATION_num(alg->impls); j++) {
864
3.69k
            impl = sk_IMPLEMENTATION_value(alg->impls, j);
865
3.69k
            if (impl != NULL
866
3.69k
                && (prov == NULL || impl->provider == prov)) {
867
3.69k
                best_impl = impl;
868
3.69k
                ret = 1;
869
3.69k
                break;
870
3.69k
            }
871
3.69k
        }
872
3.69k
        goto fin;
873
3.69k
    }
874
875
    /*
876
     * If there are optional properties specified
877
     * then run the search again, and select the provider that matches the
878
     * most options
879
     */
880
2.32k
    optional = ossl_property_has_optional(pq);
881
2.68k
    for (j = 0; j < sk_IMPLEMENTATION_num(alg->impls); j++) {
882
2.32k
        impl = sk_IMPLEMENTATION_value(alg->impls, j);
883
2.32k
        if (impl != NULL
884
2.32k
            && (prov == NULL || impl->provider == prov)) {
885
2.32k
            score = ossl_property_match_count(pq, impl->properties);
886
2.32k
            if (score > best) {
887
2.08k
                best_impl = impl;
888
2.08k
                best = score;
889
2.08k
                ret = 1;
890
2.08k
                if (!optional)
891
1.96k
                    goto fin;
892
2.08k
            }
893
2.32k
        }
894
2.32k
    }
895
6.02k
fin:
896
6.02k
    if (ret && ossl_method_up_ref(&best_impl->method)) {
897
5.77k
        *method = best_impl->method.method;
898
5.77k
        if (prov_rw != NULL)
899
5.77k
            *prov_rw = best_impl->provider;
900
5.77k
    } else {
901
245
        ret = 0;
902
245
    }
903
904
6.02k
#ifndef FIPS_MODULE
905
6.02k
    OSSL_TRACE_BEGIN(QUERY)
906
0
    {
907
0
        char buf[512];
908
0
        size_t size;
909
910
0
        size = ossl_property_list_to_string(NULL, pq, buf, 512);
911
0
        BIO_printf(trc_out, "method store query with properties %s "
912
0
                            "resolves to provider %s\n",
913
0
            size == 0 ? "none" : buf,
914
0
            best_impl == NULL ? "none" : ossl_provider_name(best_impl->provider));
915
0
    }
916
6.02k
    OSSL_TRACE_END(QUERY);
917
6.02k
#endif
918
919
6.02k
    ossl_property_unlock(sa);
920
6.02k
    ossl_property_free(p2);
921
6.02k
    return ret;
922
2.32k
}
923
924
static void ossl_method_cache_flush_alg(STORED_ALGORITHMS *sa,
925
    ALGORITHM *alg)
926
5.06k
{
927
5.06k
    impl_cache_flush_alg(alg, sa);
928
5.06k
}
929
930
static void ossl_method_cache_flush(STORED_ALGORITHMS *sa, int nid)
931
25.1k
{
932
25.1k
    ALGORITHM *alg = ossl_method_store_retrieve(sa, nid);
933
934
25.1k
    if (alg != NULL)
935
5.06k
        ossl_method_cache_flush_alg(sa, alg);
936
25.1k
}
937
938
int ossl_method_store_cache_flush_all(OSSL_METHOD_STORE *store)
939
104
{
940
520
    for (int i = 0; i < NUM_SHARDS; ++i) {
941
416
        STORED_ALGORITHMS *sa = &store->algs[i];
942
943
416
        if (!ossl_property_write_lock(sa))
944
0
            return 0;
945
416
        ossl_ht_flush(sa->cache);
946
416
        ossl_property_unlock(sa);
947
416
    }
948
949
104
    return 1;
950
104
}
951
952
/*
953
 * Generate some randomness in our hash table when we need it, since
954
 * The use of this particular code occurs before our algorithms are
955
 * registered, preventing the use of the RAND_bytes apis.
956
 * Based off of:
957
 * https://doi.org/10.18637/jss.v008.i14
958
 */
959
static ossl_inline void generate_random_seed(uint32_t *seed)
960
1.49k
{
961
1.49k
    OSSL_TIME ts;
962
963
1.49k
    if (*seed == 0) {
964
92
        *seed = OPENSSL_rdtsc();
965
92
        if (*seed == 0) {
966
0
            ts = ossl_time_now();
967
0
            *seed = (uint32_t)ts.t;
968
0
        }
969
92
    }
970
971
1.49k
    *seed ^= *seed << 13;
972
1.49k
    *seed ^= *seed >> 17;
973
1.49k
    *seed ^= *seed << 5;
974
1.49k
}
975
976
/*
977
 * Cull items from the QUERY cache.
978
 *
979
 * We don't want to let our QUERY cache grow too large, so if we grow beyond
980
 * its threshold, randomly discard some entries.
981
 *
982
 * We do this with an lru-like heuristic, and some randomness.
983
 *
984
 * the process is:
985
 * 1) Iterate over each element in the QUERY hashtable, for each element:
986
 * 2) If its used flag is set, its been recently used, so skip it.
987
 * 3) Otherwise, consult the sa->seed value.  Check the low order bit of sa->seed,
988
 *    which at cache creation is randomly generated.  If the bit is set, select the QUERY
989
 *    precomputed hash value, and delete it form the table.
990
 * 4) Shift the seed value right one bit.
991
 * 5) Repeat steps 1-4 until the number of requested entries have been removed
992
 * 6) Update our sa->seed by xoring the current sa->seed with the last hash that was eliminated.
993
 */
994
static void QUERY_cache_select_cull(ALGORITHM *alg, STORED_ALGORITHMS *sa,
995
    size_t cullcount, uint32_t seed)
996
92
{
997
92
    size_t culled = 0;
998
92
    uint64_t hash = 0;
999
92
    uint32_t used = 0;
1000
92
    QUERY *q, *qn;
1001
92
    QUERY_KEY key;
1002
1003
92
cull_again:
1004
92
    OSSL_LIST_FOREACH_DELSAFE(q, qn, lru_entry, &sa->lru_list)
1005
3.46k
    {
1006
        /*
1007
         * Skip QUERY elements that have been recently used
1008
         * reset this flag so all elements have to continuously
1009
         * demonstrate use.
1010
         */
1011
3.46k
        used = tsan_load(&q->used);
1012
3.46k
        tsan_store(&q->used, 0);
1013
3.46k
        if (used)
1014
409
            continue;
1015
        /*
1016
         * If the low order bit in the seed is set, we can delete this entry
1017
         */
1018
3.05k
        if (seed & 0x1) {
1019
            /*
1020
             * Select the hash value to delete in priority order, specific if its
1021
             * given, generic otherwise
1022
             */
1023
1.49k
            hash = (q->specific_hash != 0) ? q->specific_hash : q->generic_hash;
1024
1.49k
            HT_INIT_KEY_CACHED(&key, hash);
1025
1.49k
            if (ossl_ht_delete(sa->cache, TO_HT_KEY(&key))) {
1026
1.49k
                culled++;
1027
1.49k
                if (culled == cullcount)
1028
92
                    break;
1029
1.49k
            }
1030
1.40k
            generate_random_seed(&seed);
1031
1.55k
        } else {
1032
1.55k
            seed = seed >> 1;
1033
1.55k
        }
1034
3.05k
    }
1035
    /*
1036
     * If we didn't cull our requested number of entries
1037
     * try again.  Note that the used flag is cleared on
1038
     * all entries now, so every entry is fair game
1039
     */
1040
92
    if (culled < cullcount)
1041
0
        goto cull_again;
1042
92
}
1043
1044
static ossl_inline int ossl_method_store_cache_get_locked(OSSL_METHOD_STORE *store, OSSL_PROVIDER *prov,
1045
    int nid, const char *prop_query, size_t keylen, STORED_ALGORITHMS *sa, QUERY **post_insert,
1046
    void **method)
1047
4.14M
{
1048
4.14M
    ALGORITHM *alg;
1049
4.14M
    QUERY *r = NULL;
1050
4.14M
    int res = 0;
1051
4.14M
    QUERY_KEY key;
1052
4.14M
    HT_VALUE *v = NULL;
1053
4.14M
    uint64_t generic_hash;
1054
4.14M
#ifdef ALLOW_VLA
1055
4.14M
    uint8_t keybuf[keylen];
1056
#else
1057
    uint8_t *keybuf;
1058
#endif
1059
1060
4.14M
    *post_insert = NULL;
1061
1062
#ifndef ALLOW_VLA
1063
    keybuf = OPENSSL_malloc(keylen);
1064
    if (keybuf == NULL)
1065
        goto err;
1066
#endif
1067
1068
4.14M
    alg = ossl_method_store_retrieve(sa, nid);
1069
4.14M
    if (alg == NULL)
1070
145k
        goto err;
1071
1072
    /*
1073
     * Marshall our lookup key.
1074
     * the key is always [nid,prop_query] and may include
1075
     * the address of the provider on the end if given
1076
     */
1077
3.99M
    keylen = 0;
1078
3.99M
    memcpy(&keybuf[keylen], &nid, sizeof(int));
1079
3.99M
    keylen += sizeof(int);
1080
3.99M
    memcpy(&keybuf[keylen], prop_query, strlen(prop_query));
1081
3.99M
    keylen += strlen(prop_query);
1082
3.99M
    if (prov != NULL) {
1083
39.2k
        memcpy(&keybuf[keylen], &prov, sizeof(OSSL_PROVIDER *));
1084
39.2k
        keylen += sizeof(OSSL_PROVIDER *);
1085
39.2k
    }
1086
1087
3.99M
    HT_INIT_KEY_EXTERNAL(&key, keybuf, keylen);
1088
1089
3.99M
    r = ossl_ht_cache_QUERY_get(sa->cache, TO_HT_KEY(&key), &v);
1090
3.99M
    if (r == NULL) {
1091
1.48k
        if (prov != NULL)
1092
19
            goto err;
1093
        /*
1094
         * We don't have a providerless entry for this lookup
1095
         * (it likely got culled), so we need to rebuild one
1096
         * we can used the cached hash value from the above lookup
1097
         * to scan the lru list for a good match
1098
         */
1099
1.46k
        generic_hash = HT_KEY_GET_HASH(&key);
1100
1.46k
        OSSL_LIST_FOREACH(r, lru_entry, &sa->lru_list)
1101
119k
        {
1102
119k
            if (r->generic_hash == generic_hash) {
1103
                /*
1104
                 * We found an entry for which the generic_hash
1105
                 * (that is the hash of the [nid,propquery] tuple
1106
                 * matches what we tried, and failed to look up
1107
                 * above, so duplicate this as our new generic lookup
1108
                 */
1109
0
                r = OPENSSL_memdup(r, sizeof(*r));
1110
0
                if (r == NULL)
1111
0
                    goto err;
1112
0
                r->generic_hash = generic_hash;
1113
0
                r->specific_hash = 0;
1114
0
                r->used = 0;
1115
0
                ossl_list_lru_entry_init_elem(r);
1116
0
                HT_INIT_KEY_CACHED(&key, generic_hash);
1117
                /*
1118
                 * We need to take a reference here to represent the hash table
1119
                 * ownership.  We will take a second reference below as the caller
1120
                 * owns it as well
1121
                 */
1122
0
                if (!ossl_method_up_ref(&r->method)) {
1123
0
                    impl_cache_free(r);
1124
0
                    r = NULL;
1125
0
                }
1126
                /*
1127
                 * Inform the caller that we need to insert this newly created
1128
                 * QUERY into the hash table.  We do this because we only
1129
                 * hold the read lock here, so after the caller drops it, we
1130
                 * can then take the write lock to do the insert
1131
                 */
1132
0
                *post_insert = r;
1133
0
                break;
1134
0
            }
1135
119k
        }
1136
1.46k
        if (r == NULL)
1137
1.46k
            goto err;
1138
1.46k
    }
1139
3.99M
    tsan_store(&r->used, 1);
1140
3.99M
    if (ossl_method_up_ref(&r->method)) {
1141
3.99M
        *method = r->method.method;
1142
3.99M
        res = 1;
1143
3.99M
    }
1144
4.14M
err:
1145
#ifndef ALLOW_VLA
1146
    OPENSSL_free(keybuf);
1147
#endif
1148
4.14M
    return res;
1149
3.99M
}
1150
1151
int ossl_method_store_cache_get(OSSL_METHOD_STORE *store, OSSL_PROVIDER *prov,
1152
    int nid, const char *prop_query, void **method)
1153
4.14M
{
1154
4.14M
    size_t keylen = sizeof(int) + ((prop_query == NULL) ? 1 : strlen(prop_query))
1155
4.14M
        + sizeof(OSSL_PROVIDER *);
1156
4.14M
    int ret;
1157
4.14M
    STORED_ALGORITHMS *sa;
1158
4.14M
    QUERY *post_insert = NULL;
1159
4.14M
    QUERY_KEY key;
1160
1161
4.14M
    if (nid <= 0 || store == NULL || prop_query == NULL)
1162
0
        return 0;
1163
1164
4.14M
    if (keylen > MAX_PROP_QUERY)
1165
128
        return 0;
1166
1167
4.14M
    sa = stored_algs_shard(store, nid);
1168
4.14M
    if (!ossl_property_read_lock(sa))
1169
0
        return 0;
1170
1171
    /*
1172
     * Note: We've bifurcated this function into a locked and unlocked variant
1173
     * Not because of any specific need to do the locked work from some other location,
1174
     * but rather because in the interests of performance, we allocate a buffer on the
1175
     * stack which can be an arbitrary size.  In order to allow for clamping of that
1176
     * value, we check the keylen above for size limit, and then use this call to create
1177
     * a new stack frame in which we can safely do that stack allocation.
1178
     */
1179
4.14M
    ret = ossl_method_store_cache_get_locked(store, prov, nid, prop_query, keylen, sa,
1180
4.14M
        &post_insert, method);
1181
1182
4.14M
    ossl_property_unlock(sa);
1183
1184
4.14M
    if (ret == 1 && post_insert != NULL) {
1185
0
        if (!ossl_property_write_lock(sa)) {
1186
0
            impl_cache_free(post_insert);
1187
0
            *method = NULL;
1188
0
            ret = 0;
1189
0
        } else {
1190
0
            HT_INIT_KEY_CACHED(&key, post_insert->generic_hash);
1191
1192
0
            if (!ossl_ht_cache_QUERY_insert(sa->cache, TO_HT_KEY(&key), post_insert, NULL)) {
1193
                /*
1194
                 * We raced with another thread that added the same QUERY, pitch this one
1195
                 */
1196
0
                impl_cache_free(post_insert);
1197
0
                *method = NULL;
1198
0
                ret = 0;
1199
0
            } else {
1200
0
                ossl_list_lru_entry_insert_tail(&sa->lru_list, post_insert);
1201
0
            }
1202
0
            ossl_property_unlock(sa);
1203
0
        }
1204
0
    }
1205
4.14M
    return ret;
1206
4.14M
}
1207
1208
static ossl_inline int ossl_method_store_cache_set_locked(OSSL_METHOD_STORE *store, OSSL_PROVIDER *prov,
1209
    int nid, const char *prop_query, size_t keylen, STORED_ALGORITHMS *sa, void *method,
1210
    int (*method_up_ref)(void *),
1211
    void (*method_destruct)(void *))
1212
1.51k
{
1213
1.51k
    QUERY *old = NULL, *p = NULL;
1214
1.51k
    ALGORITHM *alg;
1215
1.51k
    QUERY_KEY key;
1216
1.51k
    int res = 1;
1217
1.51k
    size_t cullcount;
1218
1.51k
#ifdef ALLOW_VLA
1219
1.51k
    uint8_t keybuf[keylen];
1220
#else
1221
    uint8_t *keybuf;
1222
#endif
1223
1224
#ifndef ALLOW_VLA
1225
    keybuf = OPENSSL_malloc(keylen);
1226
    if (keybuf == NULL)
1227
        goto err;
1228
#endif
1229
1230
1.51k
    alg = ossl_method_store_retrieve(sa, nid);
1231
1.51k
    if (alg == NULL)
1232
0
        goto err;
1233
1.51k
    if (ossl_ht_count(sa->cache) > IMPL_CACHE_FLUSH_THRESHOLD) {
1234
92
        uint32_t seed = 0;
1235
1236
92
        generate_random_seed(&seed);
1237
        /*
1238
         * Cull between 1 and 25% of this cache
1239
         */
1240
92
        cullcount = ossl_ht_count(sa->cache);
1241
        /*
1242
         * If this cache has less than 25% of the total entries
1243
         * in the STORED_ALGORITHMS shard, don't bother culling
1244
         * Just wait until we try to add to a larger cache
1245
         */
1246
92
        if (cullcount >= 4) {
1247
92
            cullcount = seed % (cullcount >> 2);
1248
92
            cullcount = (cullcount < 1) ? 1 : cullcount;
1249
92
            QUERY_cache_select_cull(alg, sa, cullcount, seed);
1250
92
        }
1251
92
    }
1252
1253
    /*
1254
     * Marshall our lookup key
1255
     * NOTE: Provider cant be NULL here so we always add it
1256
     */
1257
1.51k
    keylen = 0;
1258
1.51k
    memcpy(&keybuf[keylen], &nid, sizeof(int));
1259
1.51k
    keylen += sizeof(int);
1260
1.51k
    memcpy(&keybuf[keylen], prop_query, strlen(prop_query));
1261
1.51k
    keylen += strlen(prop_query);
1262
1.51k
    memcpy(&keybuf[keylen], &prov, sizeof(OSSL_PROVIDER *));
1263
1.51k
    keylen += sizeof(OSSL_PROVIDER *);
1264
1265
1.51k
    HT_INIT_KEY_EXTERNAL(&key, keybuf, keylen);
1266
1267
1.51k
    if (method == NULL) {
1268
0
        ossl_ht_delete(sa->cache, TO_HT_KEY(&key));
1269
0
        goto end;
1270
0
    }
1271
1.51k
    p = OPENSSL_malloc(sizeof(*p));
1272
1.51k
    if (p != NULL) {
1273
1.51k
        p->saptr = sa;
1274
1.51k
        p->nid = nid;
1275
1.51k
        p->used = 0;
1276
1.51k
        ossl_list_lru_entry_init_elem(p);
1277
1.51k
        p->method.method = method;
1278
1.51k
        p->method.up_ref = method_up_ref;
1279
1.51k
        p->method.free = method_destruct;
1280
1.51k
        if (!ossl_method_up_ref(&p->method))
1281
0
            goto err;
1282
1283
1.51k
        if (!ossl_ht_cache_QUERY_insert(sa->cache, TO_HT_KEY(&key), p, &old)) {
1284
0
            impl_cache_free(p);
1285
0
            goto err;
1286
0
        }
1287
1.51k
        p->specific_hash = HT_KEY_GET_HASH(&key);
1288
1.51k
        p->generic_hash = 0;
1289
1.51k
        if (old != NULL)
1290
1
            impl_cache_free(old);
1291
1.51k
        ossl_list_lru_entry_insert_head(&sa->lru_list, p);
1292
        /*
1293
         * We also want to add this method into the cache against a key computed _only_
1294
         * from nid and property query.  This lets us match in the event someone does a lookup
1295
         * against a NULL provider (i.e. the "any provided alg will do" match
1296
         */
1297
1.51k
        keylen -= sizeof(OSSL_PROVIDER *);
1298
1.51k
        HT_INIT_KEY_EXTERNAL(&key, keybuf, keylen);
1299
1.51k
        old = p;
1300
1.51k
        p = OPENSSL_memdup(p, sizeof(*p));
1301
1.51k
        if (p == NULL)
1302
0
            goto err;
1303
1304
1.51k
        ossl_list_lru_entry_init_elem(p);
1305
1.51k
        if (!ossl_method_up_ref(&p->method))
1306
0
            goto err;
1307
1.51k
        if (ossl_ht_cache_QUERY_insert(sa->cache, TO_HT_KEY(&key), p, NULL)) {
1308
1.50k
            p->specific_hash = 0;
1309
1.50k
            p->generic_hash = old->generic_hash = HT_KEY_GET_HASH(&key);
1310
1.50k
            ossl_list_lru_entry_insert_tail(&sa->lru_list, p);
1311
1.50k
        } else {
1312
6
            impl_cache_free(p);
1313
6
            p = NULL;
1314
6
            goto err;
1315
6
        }
1316
1317
1.50k
        goto end;
1318
1.51k
    }
1319
6
err:
1320
6
    res = 0;
1321
6
    OPENSSL_free(p);
1322
1.51k
end:
1323
#ifndef ALLOW_VLA
1324
    OPENSSL_free(keybuf);
1325
#endif
1326
1.51k
    return res;
1327
6
}
1328
1329
int ossl_method_store_cache_set(OSSL_METHOD_STORE *store, OSSL_PROVIDER *prov,
1330
    int nid, const char *prop_query, void *method,
1331
    int (*method_up_ref)(void *),
1332
    void (*method_destruct)(void *))
1333
1.63k
{
1334
1.63k
    STORED_ALGORITHMS *sa;
1335
1.63k
    int res = 1;
1336
1.63k
    size_t keylen = sizeof(int) + ((prop_query == NULL) ? 1 : strlen(prop_query))
1337
1.63k
        + sizeof(OSSL_PROVIDER *);
1338
1339
1.63k
    if (nid <= 0 || store == NULL || prop_query == NULL)
1340
0
        return 0;
1341
1342
1.63k
    if (!ossl_assert(prov != NULL))
1343
0
        return 0;
1344
1345
1.63k
    if (keylen > MAX_PROP_QUERY)
1346
127
        return 0;
1347
1348
1.51k
    sa = stored_algs_shard(store, nid);
1349
1.51k
    if (!ossl_property_write_lock(sa))
1350
0
        return 0;
1351
1352
    /*
1353
     * As with cache_get_locked, we do this to allow ourselves the opportunity to make sure
1354
     * keylen isn't so large that the stack allocation of keylen bytes will case a stack
1355
     * overflow
1356
     */
1357
1.51k
    res = ossl_method_store_cache_set_locked(store, prov, nid, prop_query, keylen, sa, method,
1358
1.51k
        method_up_ref, method_destruct);
1359
1.51k
    ossl_property_unlock(sa);
1360
1.51k
    return res;
1361
1.51k
}