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