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