xref: /openssl/crypto/ex_data.c (revision d4f22a91)
1 /*
2  * Copyright 1995-2023 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9 
10 #include <stdlib.h>
11 #include "crypto/cryptlib.h"
12 #include "internal/thread_once.h"
13 
ossl_do_ex_data_init(OSSL_LIB_CTX * ctx)14 int ossl_do_ex_data_init(OSSL_LIB_CTX *ctx)
15 {
16     OSSL_EX_DATA_GLOBAL *global = ossl_lib_ctx_get_ex_data_global(ctx);
17 
18     if (global == NULL)
19         return 0;
20 
21     global->ex_data_lock = CRYPTO_THREAD_lock_new();
22     return global->ex_data_lock != NULL;
23 }
24 
25 /*
26  * Return the EX_CALLBACKS from the |ex_data| array that corresponds to
27  * a given class.  On success, *holds the lock.*
28  * The |global| parameter is assumed to be non null (checked by the caller).
29  * If |read| is 1 then a read lock is obtained. Otherwise it is a write lock.
30  */
get_and_lock(OSSL_EX_DATA_GLOBAL * global,int class_index,int read)31 static EX_CALLBACKS *get_and_lock(OSSL_EX_DATA_GLOBAL *global, int class_index,
32                                   int read)
33 {
34     EX_CALLBACKS *ip;
35 
36     if (class_index < 0 || class_index >= CRYPTO_EX_INDEX__COUNT) {
37         ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
38         return NULL;
39     }
40 
41     if (global->ex_data_lock == NULL) {
42         /*
43          * If we get here, someone (who?) cleaned up the lock, so just
44          * treat it as an error.
45          */
46          return NULL;
47     }
48 
49     if (read) {
50         if (!CRYPTO_THREAD_read_lock(global->ex_data_lock))
51             return NULL;
52     } else {
53         if (!CRYPTO_THREAD_write_lock(global->ex_data_lock))
54             return NULL;
55     }
56 
57     ip = &global->ex_data[class_index];
58     return ip;
59 }
60 
cleanup_cb(EX_CALLBACK * funcs)61 static void cleanup_cb(EX_CALLBACK *funcs)
62 {
63     OPENSSL_free(funcs);
64 }
65 
66 /*
67  * Release all "ex_data" state to prevent memory leaks. This can't be made
68  * thread-safe without overhauling a lot of stuff, and shouldn't really be
69  * called under potential race-conditions anyway (it's for program shutdown
70  * after all).
71  */
ossl_crypto_cleanup_all_ex_data_int(OSSL_LIB_CTX * ctx)72 void ossl_crypto_cleanup_all_ex_data_int(OSSL_LIB_CTX *ctx)
73 {
74     int i;
75     OSSL_EX_DATA_GLOBAL *global = ossl_lib_ctx_get_ex_data_global(ctx);
76 
77     if (global == NULL)
78         return;
79 
80     for (i = 0; i < CRYPTO_EX_INDEX__COUNT; ++i) {
81         EX_CALLBACKS *ip = &global->ex_data[i];
82 
83         sk_EX_CALLBACK_pop_free(ip->meth, cleanup_cb);
84         ip->meth = NULL;
85     }
86 
87     CRYPTO_THREAD_lock_free(global->ex_data_lock);
88     global->ex_data_lock = NULL;
89 }
90 
91 
92 /*
93  * Unregister a new index by replacing the callbacks with no-ops.
94  * Any in-use instances are leaked.
95  */
dummy_new(void * parent,void * ptr,CRYPTO_EX_DATA * ad,int idx,long argl,void * argp)96 static void dummy_new(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx,
97                      long argl, void *argp)
98 {
99 }
100 
dummy_free(void * parent,void * ptr,CRYPTO_EX_DATA * ad,int idx,long argl,void * argp)101 static void dummy_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx,
102                        long argl, void *argp)
103 {
104 }
105 
dummy_dup(CRYPTO_EX_DATA * to,const CRYPTO_EX_DATA * from,void ** from_d,int idx,long argl,void * argp)106 static int dummy_dup(CRYPTO_EX_DATA *to, const CRYPTO_EX_DATA *from,
107                      void **from_d, int idx,
108                      long argl, void *argp)
109 {
110     return 1;
111 }
112 
ossl_crypto_free_ex_index_ex(OSSL_LIB_CTX * ctx,int class_index,int idx)113 int ossl_crypto_free_ex_index_ex(OSSL_LIB_CTX *ctx, int class_index, int idx)
114 {
115     EX_CALLBACKS *ip;
116     EX_CALLBACK *a;
117     int toret = 0;
118     OSSL_EX_DATA_GLOBAL *global = ossl_lib_ctx_get_ex_data_global(ctx);
119 
120     if (global == NULL)
121         return 0;
122 
123     ip = get_and_lock(global, class_index, 0);
124     if (ip == NULL)
125         return 0;
126 
127     if (idx < 0 || idx >= sk_EX_CALLBACK_num(ip->meth))
128         goto err;
129     a = sk_EX_CALLBACK_value(ip->meth, idx);
130     if (a == NULL)
131         goto err;
132     a->new_func = dummy_new;
133     a->dup_func = dummy_dup;
134     a->free_func = dummy_free;
135     toret = 1;
136 err:
137     CRYPTO_THREAD_unlock(global->ex_data_lock);
138     return toret;
139 }
140 
CRYPTO_free_ex_index(int class_index,int idx)141 int CRYPTO_free_ex_index(int class_index, int idx)
142 {
143     return ossl_crypto_free_ex_index_ex(NULL, class_index, idx);
144 }
145 
146 /*
147  * Register a new index.
148  */
ossl_crypto_get_ex_new_index_ex(OSSL_LIB_CTX * ctx,int class_index,long argl,void * argp,CRYPTO_EX_new * new_func,CRYPTO_EX_dup * dup_func,CRYPTO_EX_free * free_func,int priority)149 int ossl_crypto_get_ex_new_index_ex(OSSL_LIB_CTX *ctx, int class_index,
150                                     long argl, void *argp,
151                                     CRYPTO_EX_new *new_func,
152                                     CRYPTO_EX_dup *dup_func,
153                                     CRYPTO_EX_free *free_func,
154                                     int priority)
155 {
156     int toret = -1;
157     EX_CALLBACK *a;
158     EX_CALLBACKS *ip;
159     OSSL_EX_DATA_GLOBAL *global = ossl_lib_ctx_get_ex_data_global(ctx);
160 
161     if (global == NULL)
162         return -1;
163 
164     ip = get_and_lock(global, class_index, 0);
165     if (ip == NULL)
166         return -1;
167 
168     if (ip->meth == NULL) {
169         ip->meth = sk_EX_CALLBACK_new_null();
170         /* We push an initial value on the stack because the SSL
171          * "app_data" routines use ex_data index zero.  See RT 3710. */
172         if (ip->meth == NULL
173             || !sk_EX_CALLBACK_push(ip->meth, NULL)) {
174             sk_EX_CALLBACK_free(ip->meth);
175             ip->meth = NULL;
176             ERR_raise(ERR_LIB_CRYPTO, ERR_R_CRYPTO_LIB);
177             goto err;
178         }
179     }
180 
181     a = (EX_CALLBACK *)OPENSSL_malloc(sizeof(*a));
182     if (a == NULL)
183         goto err;
184     a->argl = argl;
185     a->argp = argp;
186     a->new_func = new_func;
187     a->dup_func = dup_func;
188     a->free_func = free_func;
189     a->priority = priority;
190 
191     if (!sk_EX_CALLBACK_push(ip->meth, NULL)) {
192         ERR_raise(ERR_LIB_CRYPTO, ERR_R_CRYPTO_LIB);
193         OPENSSL_free(a);
194         goto err;
195     }
196     toret = sk_EX_CALLBACK_num(ip->meth) - 1;
197     (void)sk_EX_CALLBACK_set(ip->meth, toret, a);
198 
199  err:
200     CRYPTO_THREAD_unlock(global->ex_data_lock);
201     return toret;
202 }
203 
CRYPTO_get_ex_new_index(int class_index,long argl,void * argp,CRYPTO_EX_new * new_func,CRYPTO_EX_dup * dup_func,CRYPTO_EX_free * free_func)204 int CRYPTO_get_ex_new_index(int class_index, long argl, void *argp,
205                             CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func,
206                             CRYPTO_EX_free *free_func)
207 {
208     return ossl_crypto_get_ex_new_index_ex(NULL, class_index, argl, argp,
209                                            new_func, dup_func, free_func, 0);
210 }
211 
212 /*
213  * Initialise a new CRYPTO_EX_DATA for use in a particular class - including
214  * calling new() callbacks for each index in the class used by this variable
215  * Thread-safe by copying a class's array of "EX_CALLBACK" entries
216  * in the lock, then using them outside the lock. Note this only applies
217  * to the global "ex_data" state (ie. class definitions), not 'ad' itself.
218  */
ossl_crypto_new_ex_data_ex(OSSL_LIB_CTX * ctx,int class_index,void * obj,CRYPTO_EX_DATA * ad)219 int ossl_crypto_new_ex_data_ex(OSSL_LIB_CTX *ctx, int class_index, void *obj,
220                                CRYPTO_EX_DATA *ad)
221 {
222     int mx, i;
223     void *ptr;
224     EX_CALLBACK **storage = NULL;
225     EX_CALLBACK *stack[10];
226     EX_CALLBACKS *ip;
227     OSSL_EX_DATA_GLOBAL *global = ossl_lib_ctx_get_ex_data_global(ctx);
228 
229     if (global == NULL)
230         return 0;
231 
232     ip = get_and_lock(global, class_index, 1);
233     if (ip == NULL)
234         return 0;
235 
236     ad->ctx = ctx;
237     ad->sk = NULL;
238     mx = sk_EX_CALLBACK_num(ip->meth);
239     if (mx > 0) {
240         if (mx < (int)OSSL_NELEM(stack))
241             storage = stack;
242         else
243             storage = OPENSSL_malloc(sizeof(*storage) * mx);
244         if (storage != NULL)
245             for (i = 0; i < mx; i++)
246                 storage[i] = sk_EX_CALLBACK_value(ip->meth, i);
247     }
248     CRYPTO_THREAD_unlock(global->ex_data_lock);
249 
250     if (mx > 0 && storage == NULL)
251         return 0;
252     for (i = 0; i < mx; i++) {
253         if (storage[i] != NULL && storage[i]->new_func != NULL) {
254             ptr = CRYPTO_get_ex_data(ad, i);
255             storage[i]->new_func(obj, ptr, ad, i,
256                                  storage[i]->argl, storage[i]->argp);
257         }
258     }
259     if (storage != stack)
260         OPENSSL_free(storage);
261     return 1;
262 }
263 
CRYPTO_new_ex_data(int class_index,void * obj,CRYPTO_EX_DATA * ad)264 int CRYPTO_new_ex_data(int class_index, void *obj, CRYPTO_EX_DATA *ad)
265 {
266     return ossl_crypto_new_ex_data_ex(NULL, class_index, obj, ad);
267 }
268 
269 /*
270  * Duplicate a CRYPTO_EX_DATA variable - including calling dup() callbacks
271  * for each index in the class used by this variable
272  */
CRYPTO_dup_ex_data(int class_index,CRYPTO_EX_DATA * to,const CRYPTO_EX_DATA * from)273 int CRYPTO_dup_ex_data(int class_index, CRYPTO_EX_DATA *to,
274                        const CRYPTO_EX_DATA *from)
275 {
276     int mx, j, i;
277     void *ptr;
278     EX_CALLBACK *stack[10];
279     EX_CALLBACK **storage = NULL;
280     EX_CALLBACKS *ip;
281     int toret = 0;
282     OSSL_EX_DATA_GLOBAL *global;
283 
284     to->ctx = from->ctx;
285     if (from->sk == NULL)
286         /* Nothing to copy over */
287         return 1;
288 
289     global = ossl_lib_ctx_get_ex_data_global(from->ctx);
290     if (global == NULL)
291         return 0;
292 
293     ip = get_and_lock(global, class_index, 1);
294     if (ip == NULL)
295         return 0;
296 
297     mx = sk_EX_CALLBACK_num(ip->meth);
298     j = sk_void_num(from->sk);
299     if (j < mx)
300         mx = j;
301     if (mx > 0) {
302         if (mx < (int)OSSL_NELEM(stack))
303             storage = stack;
304         else
305             storage = OPENSSL_malloc(sizeof(*storage) * mx);
306         if (storage != NULL)
307             for (i = 0; i < mx; i++)
308                 storage[i] = sk_EX_CALLBACK_value(ip->meth, i);
309     }
310     CRYPTO_THREAD_unlock(global->ex_data_lock);
311 
312     if (mx == 0)
313         return 1;
314     if (storage == NULL)
315         return 0;
316     /*
317      * Make sure the ex_data stack is at least |mx| elements long to avoid
318      * issues in the for loop that follows; so go get the |mx|'th element
319      * (if it does not exist CRYPTO_get_ex_data() returns NULL), and assign
320      * to itself. This is normally a no-op; but ensures the stack is the
321      * proper size
322      */
323     if (!CRYPTO_set_ex_data(to, mx - 1, CRYPTO_get_ex_data(to, mx - 1)))
324         goto err;
325 
326     for (i = 0; i < mx; i++) {
327         ptr = CRYPTO_get_ex_data(from, i);
328         if (storage[i] != NULL && storage[i]->dup_func != NULL)
329             if (!storage[i]->dup_func(to, from, &ptr, i,
330                                       storage[i]->argl, storage[i]->argp))
331                 goto err;
332         CRYPTO_set_ex_data(to, i, ptr);
333     }
334     toret = 1;
335  err:
336     if (storage != stack)
337         OPENSSL_free(storage);
338     return toret;
339 }
340 
341 struct ex_callback_entry {
342     const EX_CALLBACK *excb;
343     int index;
344 };
345 
ex_callback_compare(const void * a,const void * b)346 static int ex_callback_compare(const void *a, const void *b)
347 {
348     const struct ex_callback_entry *ap = (const struct ex_callback_entry *)a;
349     const struct ex_callback_entry *bp = (const struct ex_callback_entry *)b;
350 
351     if (ap->excb == bp->excb)
352         return 0;
353 
354     if (ap->excb == NULL)
355         return 1;
356     if (bp->excb == NULL)
357         return -1;
358     if (ap->excb->priority == bp->excb->priority)
359         return 0;
360     return ap->excb->priority > bp->excb->priority ? -1 : 1;
361 }
362 
363 /*
364  * Cleanup a CRYPTO_EX_DATA variable - including calling free() callbacks for
365  * each index in the class used by this variable
366  */
CRYPTO_free_ex_data(int class_index,void * obj,CRYPTO_EX_DATA * ad)367 void CRYPTO_free_ex_data(int class_index, void *obj, CRYPTO_EX_DATA *ad)
368 {
369     int mx, i;
370     EX_CALLBACKS *ip;
371     void *ptr;
372     const EX_CALLBACK *f;
373     struct ex_callback_entry stack[10];
374     struct ex_callback_entry *storage = NULL;
375     OSSL_EX_DATA_GLOBAL *global = ossl_lib_ctx_get_ex_data_global(ad->ctx);
376 
377     if (global == NULL)
378         goto err;
379 
380     ip = get_and_lock(global, class_index, 1);
381     if (ip == NULL)
382         goto err;
383 
384     mx = sk_EX_CALLBACK_num(ip->meth);
385     if (mx > 0) {
386         if (mx < (int)OSSL_NELEM(stack))
387             storage = stack;
388         else
389             storage = OPENSSL_malloc(sizeof(*storage) * mx);
390         if (storage != NULL)
391             for (i = 0; i < mx; i++) {
392                 storage[i].excb = sk_EX_CALLBACK_value(ip->meth, i);
393                 storage[i].index = i;
394             }
395     }
396     CRYPTO_THREAD_unlock(global->ex_data_lock);
397 
398     if (storage != NULL) {
399         /* Sort according to priority. High priority first */
400         qsort(storage, mx, sizeof(*storage), ex_callback_compare);
401         for (i = 0; i < mx; i++) {
402             f = storage[i].excb;
403 
404             if (f != NULL && f->free_func != NULL) {
405                 ptr = CRYPTO_get_ex_data(ad, storage[i].index);
406                 f->free_func(obj, ptr, ad, storage[i].index, f->argl, f->argp);
407             }
408         }
409     }
410 
411     if (storage != stack)
412         OPENSSL_free(storage);
413  err:
414     sk_void_free(ad->sk);
415     ad->sk = NULL;
416     ad->ctx = NULL;
417 }
418 
419 /*
420  * Allocate a given CRYPTO_EX_DATA item using the class specific allocation
421  * function
422  */
CRYPTO_alloc_ex_data(int class_index,void * obj,CRYPTO_EX_DATA * ad,int idx)423 int CRYPTO_alloc_ex_data(int class_index, void *obj, CRYPTO_EX_DATA *ad,
424                          int idx)
425 {
426     void *curval;
427 
428     curval = CRYPTO_get_ex_data(ad, idx);
429     /* Already there, no need to allocate */
430     if (curval != NULL)
431         return 1;
432 
433     return ossl_crypto_alloc_ex_data_intern(class_index, obj, ad, idx);
434 }
435 
ossl_crypto_alloc_ex_data_intern(int class_index,void * obj,CRYPTO_EX_DATA * ad,int idx)436 int ossl_crypto_alloc_ex_data_intern(int class_index, void *obj,
437                                      CRYPTO_EX_DATA *ad, int idx)
438 {
439     EX_CALLBACK *f;
440     EX_CALLBACKS *ip;
441     OSSL_EX_DATA_GLOBAL *global;
442 
443     global = ossl_lib_ctx_get_ex_data_global(ad->ctx);
444     if (global == NULL)
445         return 0;
446 
447     ip = get_and_lock(global, class_index, 1);
448     if (ip == NULL)
449         return 0;
450     f = sk_EX_CALLBACK_value(ip->meth, idx);
451     CRYPTO_THREAD_unlock(global->ex_data_lock);
452 
453     /*
454      * This should end up calling CRYPTO_set_ex_data(), which allocates
455      * everything necessary to support placing the new data in the right spot.
456      */
457     if (f->new_func == NULL)
458         return 0;
459 
460     f->new_func(obj, NULL, ad, idx, f->argl, f->argp);
461 
462     return 1;
463 }
464 
465 /*
466  * For a given CRYPTO_EX_DATA variable, set the value corresponding to a
467  * particular index in the class used by this variable
468  */
CRYPTO_set_ex_data(CRYPTO_EX_DATA * ad,int idx,void * val)469 int CRYPTO_set_ex_data(CRYPTO_EX_DATA *ad, int idx, void *val)
470 {
471     int i;
472 
473     if (ad->sk == NULL) {
474         if ((ad->sk = sk_void_new_null()) == NULL) {
475             ERR_raise(ERR_LIB_CRYPTO, ERR_R_CRYPTO_LIB);
476             return 0;
477         }
478     }
479 
480     for (i = sk_void_num(ad->sk); i <= idx; ++i) {
481         if (!sk_void_push(ad->sk, NULL)) {
482             ERR_raise(ERR_LIB_CRYPTO, ERR_R_CRYPTO_LIB);
483             return 0;
484         }
485     }
486     if (sk_void_set(ad->sk, idx, val) != val) {
487         /* Probably the index is out of bounds */
488         ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
489         return 0;
490     }
491     return 1;
492 }
493 
494 /*
495  * For a given CRYPTO_EX_DATA_ variable, get the value corresponding to a
496  * particular index in the class used by this variable
497  */
CRYPTO_get_ex_data(const CRYPTO_EX_DATA * ad,int idx)498 void *CRYPTO_get_ex_data(const CRYPTO_EX_DATA *ad, int idx)
499 {
500     if (ad->sk == NULL || idx >= sk_void_num(ad->sk))
501         return NULL;
502     return sk_void_value(ad->sk, idx);
503 }
504 
ossl_crypto_ex_data_get_ossl_lib_ctx(const CRYPTO_EX_DATA * ad)505 OSSL_LIB_CTX *ossl_crypto_ex_data_get_ossl_lib_ctx(const CRYPTO_EX_DATA *ad)
506 {
507     return ad->ctx;
508 }
509