/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 | } |