Invoke tear_down when exiting test_encode_tls_sct() prematurely
[openssl.git] / crypto / async / async.c
1 /*
2  * Copyright 2015-2021 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 /*
11  * Without this we start getting longjmp crashes because it thinks we're jumping
12  * up the stack when in fact we are jumping to an entirely different stack. The
13  * cost of this is not having certain buffer overrun/underrun checks etc for
14  * this source file :-(
15  */
16 #undef _FORTIFY_SOURCE
17
18 /* This must be the first #include file */
19 #include "async_local.h"
20
21 #include <openssl/err.h>
22 #include "crypto/cryptlib.h"
23 #include <string.h>
24
25 #define ASYNC_JOB_RUNNING   0
26 #define ASYNC_JOB_PAUSING   1
27 #define ASYNC_JOB_PAUSED    2
28 #define ASYNC_JOB_STOPPING  3
29
30 static CRYPTO_THREAD_LOCAL ctxkey;
31 static CRYPTO_THREAD_LOCAL poolkey;
32
33 static void async_delete_thread_state(void *arg);
34
35 static async_ctx *async_ctx_new(void)
36 {
37     async_ctx *nctx;
38
39     if (!ossl_init_thread_start(NULL, NULL, async_delete_thread_state))
40         return NULL;
41
42     nctx = OPENSSL_malloc(sizeof(*nctx));
43     if (nctx == NULL) {
44         ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
45         goto err;
46     }
47
48     async_fibre_init_dispatcher(&nctx->dispatcher);
49     nctx->currjob = NULL;
50     nctx->blocked = 0;
51     if (!CRYPTO_THREAD_set_local(&ctxkey, nctx))
52         goto err;
53
54     return nctx;
55 err:
56     OPENSSL_free(nctx);
57
58     return NULL;
59 }
60
61 async_ctx *async_get_ctx(void)
62 {
63     return (async_ctx *)CRYPTO_THREAD_get_local(&ctxkey);
64 }
65
66 static int async_ctx_free(void)
67 {
68     async_ctx *ctx;
69
70     ctx = async_get_ctx();
71
72     if (!CRYPTO_THREAD_set_local(&ctxkey, NULL))
73         return 0;
74
75     OPENSSL_free(ctx);
76
77     return 1;
78 }
79
80 static ASYNC_JOB *async_job_new(void)
81 {
82     ASYNC_JOB *job = NULL;
83
84     job = OPENSSL_zalloc(sizeof(*job));
85     if (job == NULL) {
86         ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
87         return NULL;
88     }
89
90     job->status = ASYNC_JOB_RUNNING;
91
92     return job;
93 }
94
95 static void async_job_free(ASYNC_JOB *job)
96 {
97     if (job != NULL) {
98         OPENSSL_free(job->funcargs);
99         async_fibre_free(&job->fibrectx);
100         OPENSSL_free(job);
101     }
102 }
103
104 static ASYNC_JOB *async_get_pool_job(void) {
105     ASYNC_JOB *job;
106     async_pool *pool;
107
108     pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
109     if (pool == NULL) {
110         /*
111          * Pool has not been initialised, so init with the defaults, i.e.
112          * no max size and no pre-created jobs
113          */
114         if (ASYNC_init_thread(0, 0) == 0)
115             return NULL;
116         pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
117     }
118
119     job = sk_ASYNC_JOB_pop(pool->jobs);
120     if (job == NULL) {
121         /* Pool is empty */
122         if ((pool->max_size != 0) && (pool->curr_size >= pool->max_size))
123             return NULL;
124
125         job = async_job_new();
126         if (job != NULL) {
127             if (! async_fibre_makecontext(&job->fibrectx)) {
128                 async_job_free(job);
129                 return NULL;
130             }
131             pool->curr_size++;
132         }
133     }
134     return job;
135 }
136
137 static void async_release_job(ASYNC_JOB *job) {
138     async_pool *pool;
139
140     pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
141     OPENSSL_free(job->funcargs);
142     job->funcargs = NULL;
143     sk_ASYNC_JOB_push(pool->jobs, job);
144 }
145
146 void async_start_func(void)
147 {
148     ASYNC_JOB *job;
149     async_ctx *ctx = async_get_ctx();
150
151     while (1) {
152         /* Run the job */
153         job = ctx->currjob;
154         job->ret = job->func(job->funcargs);
155
156         /* Stop the job */
157         job->status = ASYNC_JOB_STOPPING;
158         if (!async_fibre_swapcontext(&job->fibrectx,
159                                      &ctx->dispatcher, 1)) {
160             /*
161              * Should not happen. Getting here will close the thread...can't do
162              * much about it
163              */
164             ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
165         }
166     }
167 }
168
169 int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *wctx, int *ret,
170                     int (*func)(void *), void *args, size_t size)
171 {
172     async_ctx *ctx;
173     OSSL_LIB_CTX *libctx;
174
175     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
176         return ASYNC_ERR;
177
178     ctx = async_get_ctx();
179     if (ctx == NULL)
180         ctx = async_ctx_new();
181     if (ctx == NULL)
182         return ASYNC_ERR;
183
184     if (*job != NULL)
185         ctx->currjob = *job;
186
187     for (;;) {
188         if (ctx->currjob != NULL) {
189             if (ctx->currjob->status == ASYNC_JOB_STOPPING) {
190                 *ret = ctx->currjob->ret;
191                 ctx->currjob->waitctx = NULL;
192                 async_release_job(ctx->currjob);
193                 ctx->currjob = NULL;
194                 *job = NULL;
195                 return ASYNC_FINISH;
196             }
197
198             if (ctx->currjob->status == ASYNC_JOB_PAUSING) {
199                 *job = ctx->currjob;
200                 ctx->currjob->status = ASYNC_JOB_PAUSED;
201                 ctx->currjob = NULL;
202                 return ASYNC_PAUSE;
203             }
204
205             if (ctx->currjob->status == ASYNC_JOB_PAUSED) {
206                 if (*job == NULL)
207                     return ASYNC_ERR;
208                 ctx->currjob = *job;
209
210                 /*
211                  * Restore the default libctx to what it was the last time the
212                  * fibre ran
213                  */
214                 libctx = OSSL_LIB_CTX_set0_default(ctx->currjob->libctx);
215                 if (libctx == NULL) {
216                     /* Failed to set the default context */
217                     ERR_raise(ERR_LIB_ASYNC, ERR_R_INTERNAL_ERROR);
218                     goto err;
219                 }
220                 /* Resume previous job */
221                 if (!async_fibre_swapcontext(&ctx->dispatcher,
222                         &ctx->currjob->fibrectx, 1)) {
223                     ctx->currjob->libctx = OSSL_LIB_CTX_set0_default(libctx);
224                     ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
225                     goto err;
226                 }
227                 /*
228                  * In case the fibre changed the default libctx we set it back
229                  * again to what it was originally, and remember what it had
230                  * been changed to.
231                  */
232                 ctx->currjob->libctx = OSSL_LIB_CTX_set0_default(libctx);
233                 continue;
234             }
235
236             /* Should not happen */
237             ERR_raise(ERR_LIB_ASYNC, ERR_R_INTERNAL_ERROR);
238             async_release_job(ctx->currjob);
239             ctx->currjob = NULL;
240             *job = NULL;
241             return ASYNC_ERR;
242         }
243
244         /* Start a new job */
245         if ((ctx->currjob = async_get_pool_job()) == NULL)
246             return ASYNC_NO_JOBS;
247
248         if (args != NULL) {
249             ctx->currjob->funcargs = OPENSSL_malloc(size);
250             if (ctx->currjob->funcargs == NULL) {
251                 ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
252                 async_release_job(ctx->currjob);
253                 ctx->currjob = NULL;
254                 return ASYNC_ERR;
255             }
256             memcpy(ctx->currjob->funcargs, args, size);
257         } else {
258             ctx->currjob->funcargs = NULL;
259         }
260
261         ctx->currjob->func = func;
262         ctx->currjob->waitctx = wctx;
263         libctx = ossl_lib_ctx_get_concrete(NULL);
264         if (!async_fibre_swapcontext(&ctx->dispatcher,
265                 &ctx->currjob->fibrectx, 1)) {
266             ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
267             goto err;
268         }
269         /*
270          * In case the fibre changed the default libctx we set it back again
271          * to what it was, and remember what it had been changed to.
272          */
273         ctx->currjob->libctx = OSSL_LIB_CTX_set0_default(libctx);
274     }
275
276 err:
277     async_release_job(ctx->currjob);
278     ctx->currjob = NULL;
279     *job = NULL;
280     return ASYNC_ERR;
281 }
282
283 int ASYNC_pause_job(void)
284 {
285     ASYNC_JOB *job;
286     async_ctx *ctx = async_get_ctx();
287
288     if (ctx == NULL
289             || ctx->currjob == NULL
290             || ctx->blocked) {
291         /*
292          * Could be we've deliberately not been started within a job so this is
293          * counted as success.
294          */
295         return 1;
296     }
297
298     job = ctx->currjob;
299     job->status = ASYNC_JOB_PAUSING;
300
301     if (!async_fibre_swapcontext(&job->fibrectx,
302                                  &ctx->dispatcher, 1)) {
303         ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
304         return 0;
305     }
306     /* Reset counts of added and deleted fds */
307     async_wait_ctx_reset_counts(job->waitctx);
308
309     return 1;
310 }
311
312 static void async_empty_pool(async_pool *pool)
313 {
314     ASYNC_JOB *job;
315
316     if (pool == NULL || pool->jobs == NULL)
317         return;
318
319     do {
320         job = sk_ASYNC_JOB_pop(pool->jobs);
321         async_job_free(job);
322     } while (job);
323 }
324
325 int async_init(void)
326 {
327     if (!CRYPTO_THREAD_init_local(&ctxkey, NULL))
328         return 0;
329
330     if (!CRYPTO_THREAD_init_local(&poolkey, NULL)) {
331         CRYPTO_THREAD_cleanup_local(&ctxkey);
332         return 0;
333     }
334
335     return 1;
336 }
337
338 void async_deinit(void)
339 {
340     CRYPTO_THREAD_cleanup_local(&ctxkey);
341     CRYPTO_THREAD_cleanup_local(&poolkey);
342 }
343
344 int ASYNC_init_thread(size_t max_size, size_t init_size)
345 {
346     async_pool *pool;
347     size_t curr_size = 0;
348
349     if (init_size > max_size) {
350         ERR_raise(ERR_LIB_ASYNC, ASYNC_R_INVALID_POOL_SIZE);
351         return 0;
352     }
353
354     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
355         return 0;
356
357     if (!ossl_init_thread_start(NULL, NULL, async_delete_thread_state))
358         return 0;
359
360     pool = OPENSSL_zalloc(sizeof(*pool));
361     if (pool == NULL) {
362         ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
363         return 0;
364     }
365
366     pool->jobs = sk_ASYNC_JOB_new_reserve(NULL, init_size);
367     if (pool->jobs == NULL) {
368         ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
369         OPENSSL_free(pool);
370         return 0;
371     }
372
373     pool->max_size = max_size;
374
375     /* Pre-create jobs as required */
376     while (init_size--) {
377         ASYNC_JOB *job;
378         job = async_job_new();
379         if (job == NULL || !async_fibre_makecontext(&job->fibrectx)) {
380             /*
381              * Not actually fatal because we already created the pool, just
382              * skip creation of any more jobs
383              */
384             async_job_free(job);
385             break;
386         }
387         job->funcargs = NULL;
388         sk_ASYNC_JOB_push(pool->jobs, job); /* Cannot fail due to reserve */
389         curr_size++;
390     }
391     pool->curr_size = curr_size;
392     if (!CRYPTO_THREAD_set_local(&poolkey, pool)) {
393         ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SET_POOL);
394         goto err;
395     }
396
397     return 1;
398 err:
399     async_empty_pool(pool);
400     sk_ASYNC_JOB_free(pool->jobs);
401     OPENSSL_free(pool);
402     return 0;
403 }
404
405 static void async_delete_thread_state(void *arg)
406 {
407     async_pool *pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
408
409     if (pool != NULL) {
410         async_empty_pool(pool);
411         sk_ASYNC_JOB_free(pool->jobs);
412         OPENSSL_free(pool);
413         CRYPTO_THREAD_set_local(&poolkey, NULL);
414     }
415     async_local_cleanup();
416     async_ctx_free();
417 }
418
419 void ASYNC_cleanup_thread(void)
420 {
421     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
422         return;
423
424     async_delete_thread_state(NULL);
425 }
426
427 ASYNC_JOB *ASYNC_get_current_job(void)
428 {
429     async_ctx *ctx;
430
431     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
432         return NULL;
433
434     ctx = async_get_ctx();
435     if (ctx == NULL)
436         return NULL;
437
438     return ctx->currjob;
439 }
440
441 ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job)
442 {
443     return job->waitctx;
444 }
445
446 void ASYNC_block_pause(void)
447 {
448     async_ctx *ctx;
449
450     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
451         return;
452
453     ctx = async_get_ctx();
454     if (ctx == NULL || ctx->currjob == NULL) {
455         /*
456          * We're not in a job anyway so ignore this
457          */
458         return;
459     }
460     ctx->blocked++;
461 }
462
463 void ASYNC_unblock_pause(void)
464 {
465     async_ctx *ctx;
466
467     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
468         return;
469
470     ctx = async_get_ctx();
471     if (ctx == NULL || ctx->currjob == NULL) {
472         /*
473          * We're not in a job anyway so ignore this
474          */
475         return;
476     }
477     if (ctx->blocked > 0)
478         ctx->blocked--;
479 }