Coverage Report

Created: 2026-04-29 06:57

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