]> git.ipfire.org Git - thirdparty/openssl.git/blob - crypto/property/property.c
threads_pthread.c: change inline to ossl_inline
[thirdparty/openssl.git] / crypto / property / property.c
1 /*
2 * Copyright 2019 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 "internal/property.h"
16 #include "internal/ctype.h"
17 #include <openssl/lhash.h>
18 #include <openssl/rand.h>
19 #include "internal/thread_once.h"
20 #include "internal/lhash.h"
21 #include "internal/sparse_array.h"
22 #include "property_lcl.h"
23
24 /* The number of elements in the query cache before we initiate a flush */
25 #define IMPL_CACHE_FLUSH_THRESHOLD 500
26
27 typedef struct {
28 const OSSL_PROVIDER *provider;
29 OSSL_PROPERTY_LIST *properties;
30 void *method;
31 void (*method_destruct)(void *);
32 } IMPLEMENTATION;
33
34 DEFINE_STACK_OF(IMPLEMENTATION)
35
36 typedef struct {
37 const char *query;
38 void *method;
39 char body[1];
40 } QUERY;
41
42 DEFINE_LHASH_OF(QUERY);
43
44 typedef struct {
45 int nid;
46 STACK_OF(IMPLEMENTATION) *impls;
47 LHASH_OF(QUERY) *cache;
48 } ALGORITHM;
49
50 struct ossl_method_store_st {
51 OPENSSL_CTX *ctx;
52 size_t nelem;
53 SPARSE_ARRAY_OF(ALGORITHM) *algs;
54 OSSL_PROPERTY_LIST *global_properties;
55 int need_flush;
56 unsigned int nbits;
57 unsigned char rand_bits[(IMPL_CACHE_FLUSH_THRESHOLD + 7) / 8];
58 CRYPTO_RWLOCK *lock;
59 };
60
61 typedef struct {
62 LHASH_OF(QUERY) *cache;
63 size_t nelem;
64 uint32_t seed;
65 } IMPL_CACHE_FLUSH;
66
67 DEFINE_SPARSE_ARRAY_OF(ALGORITHM);
68
69 static void ossl_method_cache_flush(OSSL_METHOD_STORE *store, int nid);
70 static void ossl_method_cache_flush_all(OSSL_METHOD_STORE *c);
71
72 int ossl_property_read_lock(OSSL_METHOD_STORE *p)
73 {
74 return p != NULL ? CRYPTO_THREAD_read_lock(p->lock) : 0;
75 }
76
77 int ossl_property_write_lock(OSSL_METHOD_STORE *p)
78 {
79 return p != NULL ? CRYPTO_THREAD_write_lock(p->lock) : 0;
80 }
81
82 int ossl_property_unlock(OSSL_METHOD_STORE *p)
83 {
84 return p != 0 ? CRYPTO_THREAD_unlock(p->lock) : 0;
85 }
86
87 static openssl_ctx_run_once_fn do_method_store_init;
88 int do_method_store_init(OPENSSL_CTX *ctx)
89 {
90 return ossl_property_parse_init(ctx);
91 }
92
93 static unsigned long query_hash(const QUERY *a)
94 {
95 return OPENSSL_LH_strhash(a->query);
96 }
97
98 static int query_cmp(const QUERY *a, const QUERY *b)
99 {
100 return strcmp(a->query, b->query);
101 }
102
103 static void impl_free(IMPLEMENTATION *impl)
104 {
105 if (impl != NULL) {
106 if (impl->method_destruct)
107 impl->method_destruct(impl->method);
108 OPENSSL_free(impl);
109 }
110 }
111
112 static void impl_cache_free(QUERY *elem)
113 {
114 OPENSSL_free(elem);
115 }
116
117 static void alg_cleanup(ossl_uintmax_t idx, ALGORITHM *a)
118 {
119 if (a != NULL) {
120 sk_IMPLEMENTATION_pop_free(a->impls, &impl_free);
121 lh_QUERY_doall(a->cache, &impl_cache_free);
122 lh_QUERY_free(a->cache);
123 OPENSSL_free(a);
124 }
125 }
126
127 /*
128 * The OPENSSL_CTX param here allows access to underlying property data needed
129 * for computation
130 */
131 OSSL_METHOD_STORE *ossl_method_store_new(OPENSSL_CTX *ctx)
132 {
133 OSSL_METHOD_STORE *res;
134
135 if (!openssl_ctx_run_once(ctx,
136 OPENSSL_CTX_METHOD_STORE_RUN_ONCE_INDEX,
137 do_method_store_init))
138 return NULL;
139
140 res = OPENSSL_zalloc(sizeof(*res));
141 if (res != NULL) {
142 res->ctx = ctx;
143 if ((res->algs = ossl_sa_ALGORITHM_new()) == NULL) {
144 OPENSSL_free(res);
145 return NULL;
146 }
147 if ((res->lock = CRYPTO_THREAD_lock_new()) == NULL) {
148 OPENSSL_free(res->algs);
149 OPENSSL_free(res);
150 return NULL;
151 }
152 }
153 return res;
154 }
155
156 void ossl_method_store_free(OSSL_METHOD_STORE *store)
157 {
158 if (store != NULL) {
159 ossl_sa_ALGORITHM_doall(store->algs, &alg_cleanup);
160 ossl_sa_ALGORITHM_free(store->algs);
161 ossl_property_free(store->global_properties);
162 CRYPTO_THREAD_lock_free(store->lock);
163 OPENSSL_free(store);
164 }
165 }
166
167 static ALGORITHM *ossl_method_store_retrieve(OSSL_METHOD_STORE *store, int nid)
168 {
169 return ossl_sa_ALGORITHM_get(store->algs, nid);
170 }
171
172 static int ossl_method_store_insert(OSSL_METHOD_STORE *store, ALGORITHM *alg)
173 {
174 return ossl_sa_ALGORITHM_set(store->algs, alg->nid, alg);
175 }
176
177 int ossl_method_store_add(OSSL_METHOD_STORE *store, const OSSL_PROVIDER *prov,
178 int nid, const char *properties, void *method,
179 int (*method_up_ref)(void *),
180 void (*method_destruct)(void *))
181 {
182 ALGORITHM *alg = NULL;
183 IMPLEMENTATION *impl;
184 int ret = 0;
185 int i;
186
187 if (nid <= 0 || method == NULL || store == NULL)
188 return 0;
189 if (properties == NULL)
190 properties = "";
191
192 /* Create new entry */
193 impl = OPENSSL_malloc(sizeof(*impl));
194 if (impl == NULL)
195 return 0;
196 if (method_up_ref != NULL && !method_up_ref(method)) {
197 OPENSSL_free(impl);
198 return 0;
199 }
200 impl->provider = prov;
201 impl->method = method;
202 impl->method_destruct = method_destruct;
203
204 /*
205 * Insert into the hash table if required.
206 *
207 * A write lock is used unconditionally because we wend our way down to the
208 * property string code which isn't locking friendly.
209 */
210 ossl_property_write_lock(store);
211 ossl_method_cache_flush(store, nid);
212 if ((impl->properties = ossl_prop_defn_get(store->ctx, properties)) == NULL) {
213 impl->properties = ossl_parse_property(store->ctx, properties);
214 if (impl->properties == NULL)
215 goto err;
216 ossl_prop_defn_set(store->ctx, properties, impl->properties);
217 }
218
219 alg = ossl_method_store_retrieve(store, nid);
220 if (alg == NULL) {
221 if ((alg = OPENSSL_zalloc(sizeof(*alg))) == NULL
222 || (alg->impls = sk_IMPLEMENTATION_new_null()) == NULL
223 || (alg->cache = lh_QUERY_new(&query_hash, &query_cmp)) == NULL)
224 goto err;
225 alg->nid = nid;
226 if (!ossl_method_store_insert(store, alg))
227 goto err;
228 }
229
230 /* Push onto stack if there isn't one there already */
231 for (i = 0; i < sk_IMPLEMENTATION_num(alg->impls); i++) {
232 const IMPLEMENTATION *tmpimpl = sk_IMPLEMENTATION_value(alg->impls, i);
233
234 if (tmpimpl->provider == impl->provider
235 && tmpimpl->properties == impl->properties)
236 break;
237 }
238 if (i == sk_IMPLEMENTATION_num(alg->impls)
239 && sk_IMPLEMENTATION_push(alg->impls, impl))
240 ret = 1;
241 ossl_property_unlock(store);
242 if (ret == 0)
243 impl_free(impl);
244 return ret;
245
246 err:
247 ossl_property_unlock(store);
248 alg_cleanup(0, alg);
249 impl_free(impl);
250 return 0;
251 }
252
253 int ossl_method_store_remove(OSSL_METHOD_STORE *store, int nid,
254 const void *method)
255 {
256 ALGORITHM *alg = NULL;
257 int i;
258
259 if (nid <= 0 || method == NULL || store == NULL)
260 return 0;
261
262 ossl_property_write_lock(store);
263 ossl_method_cache_flush(store, nid);
264 alg = ossl_method_store_retrieve(store, nid);
265 if (alg == NULL) {
266 ossl_property_unlock(store);
267 return 0;
268 }
269
270 /*
271 * A sorting find then a delete could be faster but these stacks should be
272 * relatively small, so we avoid the overhead. Sorting could also surprise
273 * users when result orderings change (even though they are not guaranteed).
274 */
275 for (i = 0; i < sk_IMPLEMENTATION_num(alg->impls); i++) {
276 IMPLEMENTATION *impl = sk_IMPLEMENTATION_value(alg->impls, i);
277
278 if (impl->method == method) {
279 sk_IMPLEMENTATION_delete(alg->impls, i);
280 ossl_property_unlock(store);
281 impl_free(impl);
282 return 1;
283 }
284 }
285 ossl_property_unlock(store);
286 return 0;
287 }
288
289 int ossl_method_store_fetch(OSSL_METHOD_STORE *store, int nid,
290 const char *prop_query, void **method)
291 {
292 ALGORITHM *alg;
293 IMPLEMENTATION *impl;
294 OSSL_PROPERTY_LIST *pq = NULL, *p2;
295 int ret = 0;
296 int j, best = -1, score, optional;
297
298 #ifndef FIPS_MODE
299 OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
300 #endif
301
302 if (nid <= 0 || method == NULL || store == NULL)
303 return 0;
304
305 /*
306 * This only needs to be a read lock, because queries never create property
307 * names or value and thus don't modify any of the property string layer.
308 */
309 ossl_property_read_lock(store);
310 alg = ossl_method_store_retrieve(store, nid);
311 if (alg == NULL) {
312 ossl_property_unlock(store);
313 return 0;
314 }
315
316 if (prop_query == NULL) {
317 if ((impl = sk_IMPLEMENTATION_value(alg->impls, 0)) != NULL) {
318 *method = impl->method;
319 ret = 1;
320 }
321 goto fin;
322 }
323 pq = ossl_parse_query(store->ctx, prop_query);
324 if (pq == NULL)
325 goto fin;
326 if (store->global_properties != NULL) {
327 p2 = ossl_property_merge(pq, store->global_properties);
328 if (p2 == NULL)
329 goto fin;
330 ossl_property_free(pq);
331 pq = p2;
332 }
333 optional = ossl_property_has_optional(pq);
334 for (j = 0; j < sk_IMPLEMENTATION_num(alg->impls); j++) {
335 impl = sk_IMPLEMENTATION_value(alg->impls, j);
336 score = ossl_property_match_count(pq, impl->properties);
337 if (score > best) {
338 *method = impl->method;
339 ret = 1;
340 if (!optional)
341 goto fin;
342 best = score;
343 }
344 }
345 fin:
346 ossl_property_unlock(store);
347 ossl_property_free(pq);
348 return ret;
349 }
350
351 int ossl_method_store_set_global_properties(OSSL_METHOD_STORE *store,
352 const char *prop_query) {
353 int ret = 0;
354
355 if (store == NULL)
356 return 1;
357
358 ossl_property_write_lock(store);
359 ossl_method_cache_flush_all(store);
360 if (prop_query == NULL) {
361 ossl_property_free(store->global_properties);
362 store->global_properties = NULL;
363 ossl_property_unlock(store);
364 return 1;
365 }
366 store->global_properties = ossl_parse_query(store->ctx, prop_query);
367 ret = store->global_properties != NULL;
368 ossl_property_unlock(store);
369 return ret;
370 }
371
372 static void impl_cache_flush_alg(ossl_uintmax_t idx, ALGORITHM *alg)
373 {
374 lh_QUERY_doall(alg->cache, &impl_cache_free);
375 lh_QUERY_flush(alg->cache);
376 }
377
378 static void ossl_method_cache_flush(OSSL_METHOD_STORE *store, int nid)
379 {
380 ALGORITHM *alg = ossl_method_store_retrieve(store, nid);
381
382 if (alg != NULL) {
383 store->nelem -= lh_QUERY_num_items(alg->cache);
384 impl_cache_flush_alg(0, alg);
385 }
386 }
387
388 static void ossl_method_cache_flush_all(OSSL_METHOD_STORE *store)
389 {
390 ossl_sa_ALGORITHM_doall(store->algs, &impl_cache_flush_alg);
391 store->nelem = 0;
392 }
393
394 IMPLEMENT_LHASH_DOALL_ARG(QUERY, IMPL_CACHE_FLUSH);
395
396 /*
397 * Flush an element from the query cache (perhaps).
398 *
399 * In order to avoid taking a write lock or using atomic operations
400 * to keep accurate least recently used (LRU) or least frequently used
401 * (LFU) information, the procedure used here is to stochastically
402 * flush approximately half the cache.
403 *
404 * This procedure isn't ideal, LRU or LFU would be better. However,
405 * in normal operation, reaching a full cache would be unexpected.
406 * It means that no steady state of algorithm queries has been reached.
407 * That is, it is most likely an attack of some form. A suboptimal clearance
408 * strategy that doesn't degrade performance of the normal case is
409 * preferable to a more refined approach that imposes a performance
410 * impact.
411 */
412 static void impl_cache_flush_cache(QUERY *c, IMPL_CACHE_FLUSH *state)
413 {
414 uint32_t n;
415
416 /*
417 * Implement the 32 bit xorshift as suggested by George Marsaglia in:
418 * https://doi.org/10.18637/jss.v008.i14
419 *
420 * This is a very fast PRNG so there is no need to extract bits one at a
421 * time and use the entire value each time.
422 */
423 n = state->seed;
424 n ^= n << 13;
425 n ^= n >> 17;
426 n ^= n << 5;
427 state->seed = n;
428
429 if ((n & 1) != 0)
430 OPENSSL_free(lh_QUERY_delete(state->cache, c));
431 else
432 state->nelem++;
433 }
434
435 static void impl_cache_flush_one_alg(ossl_uintmax_t idx, ALGORITHM *alg,
436 void *v)
437 {
438 IMPL_CACHE_FLUSH *state = (IMPL_CACHE_FLUSH *)v;
439
440 state->cache = alg->cache;
441 lh_QUERY_doall_IMPL_CACHE_FLUSH(state->cache, &impl_cache_flush_cache,
442 state);
443 }
444
445 static void ossl_method_cache_flush_some(OSSL_METHOD_STORE *store)
446 {
447 IMPL_CACHE_FLUSH state;
448
449 state.nelem = 0;
450 if ((state.seed = OPENSSL_rdtsc()) == 0)
451 state.seed = 1;
452 store->need_flush = 0;
453 ossl_sa_ALGORITHM_doall_arg(store->algs, &impl_cache_flush_one_alg, &state);
454 store->nelem = state.nelem;
455 }
456
457 int ossl_method_store_cache_get(OSSL_METHOD_STORE *store, int nid,
458 const char *prop_query, void **method)
459 {
460 ALGORITHM *alg;
461 QUERY elem, *r;
462
463 if (nid <= 0 || store == NULL)
464 return 0;
465
466 ossl_property_read_lock(store);
467 alg = ossl_method_store_retrieve(store, nid);
468 if (alg == NULL) {
469 ossl_property_unlock(store);
470 return 0;
471 }
472
473 elem.query = prop_query != NULL ? prop_query : "";
474 r = lh_QUERY_retrieve(alg->cache, &elem);
475 if (r == NULL) {
476 ossl_property_unlock(store);
477 return 0;
478 }
479 *method = r->method;
480 ossl_property_unlock(store);
481 return 1;
482 }
483
484 int ossl_method_store_cache_set(OSSL_METHOD_STORE *store, int nid,
485 const char *prop_query, void *method)
486 {
487 QUERY elem, *old, *p = NULL;
488 ALGORITHM *alg;
489 size_t len;
490
491 if (nid <= 0 || store == NULL)
492 return 0;
493 if (prop_query == NULL)
494 return 1;
495
496 ossl_property_write_lock(store);
497 if (store->need_flush)
498 ossl_method_cache_flush_some(store);
499 alg = ossl_method_store_retrieve(store, nid);
500 if (alg == NULL) {
501 ossl_property_unlock(store);
502 return 0;
503 }
504
505 if (method == NULL) {
506 elem.query = prop_query;
507 if (lh_QUERY_delete(alg->cache, &elem) != NULL)
508 store->nelem--;
509 ossl_property_unlock(store);
510 return 1;
511 }
512 p = OPENSSL_malloc(sizeof(*p) + (len = strlen(prop_query)));
513 if (p != NULL) {
514 p->query = p->body;
515 p->method = method;
516 memcpy((char *)p->query, prop_query, len + 1);
517 if ((old = lh_QUERY_insert(alg->cache, p)) != NULL) {
518 OPENSSL_free(old);
519 ossl_property_unlock(store);
520 return 1;
521 }
522 if (!lh_QUERY_error(alg->cache)) {
523 if (++store->nelem >= IMPL_CACHE_FLUSH_THRESHOLD)
524 store->need_flush = 1;
525 ossl_property_unlock(store);
526 return 1;
527 }
528 }
529 ossl_property_unlock(store);
530 OPENSSL_free(p);
531 return 0;
532 }