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
30static CRYPTO_THREAD_LOCAL ctxkey;
31static CRYPTO_THREAD_LOCAL poolkey;
32
33static void async_delete_thread_state(void *arg);
34
35static 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;
55err:
56    OPENSSL_free(nctx);
57
58    return NULL;
59}
60
61async_ctx *async_get_ctx(void)
62{
63    return (async_ctx *)CRYPTO_THREAD_get_local(&ctxkey);
64}
65
66static 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
80static 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
95static 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
104static 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
137static void async_release_job(ASYNC_JOB *job) {
138    async_pool *pool;
139
140    pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
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
150void 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
177int 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                ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
260                async_release_job(ctx->currjob);
261                ctx->currjob = NULL;
262                return ASYNC_ERR;
263            }
264            memcpy(ctx->currjob->funcargs, args, size);
265        } else {
266            ctx->currjob->funcargs = NULL;
267        }
268
269        ctx->currjob->func = func;
270        ctx->currjob->waitctx = wctx;
271        libctx = ossl_lib_ctx_get_concrete(NULL);
272        if (!async_fibre_swapcontext(&ctx->dispatcher,
273                &ctx->currjob->fibrectx, 1)) {
274            ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
275            goto err;
276        }
277        /*
278         * In case the fibre changed the default libctx we set it back again
279         * to what it was, and remember what it had been changed to.
280         */
281        ctx->currjob->libctx = OSSL_LIB_CTX_set0_default(libctx);
282    }
283
284err:
285    async_release_job(ctx->currjob);
286    ctx->currjob = NULL;
287    *job = NULL;
288    return ASYNC_ERR;
289}
290
291int ASYNC_pause_job(void)
292{
293    ASYNC_JOB *job;
294    async_ctx *ctx = async_get_ctx();
295
296    if (ctx == NULL
297            || ctx->currjob == NULL
298            || ctx->blocked) {
299        /*
300         * Could be we've deliberately not been started within a job so this is
301         * counted as success.
302         */
303        return 1;
304    }
305
306    job = ctx->currjob;
307    job->status = ASYNC_JOB_PAUSING;
308
309    if (!async_fibre_swapcontext(&job->fibrectx,
310                                 &ctx->dispatcher, 1)) {
311        ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
312        return 0;
313    }
314    /* Reset counts of added and deleted fds */
315    async_wait_ctx_reset_counts(job->waitctx);
316
317    return 1;
318}
319
320static void async_empty_pool(async_pool *pool)
321{
322    ASYNC_JOB *job;
323
324    if (pool == NULL || pool->jobs == NULL)
325        return;
326
327    do {
328        job = sk_ASYNC_JOB_pop(pool->jobs);
329        async_job_free(job);
330    } while (job);
331}
332
333int async_init(void)
334{
335    if (!CRYPTO_THREAD_init_local(&ctxkey, NULL))
336        return 0;
337
338    if (!CRYPTO_THREAD_init_local(&poolkey, NULL)) {
339        CRYPTO_THREAD_cleanup_local(&ctxkey);
340        return 0;
341    }
342
343    return 1;
344}
345
346void async_deinit(void)
347{
348    CRYPTO_THREAD_cleanup_local(&ctxkey);
349    CRYPTO_THREAD_cleanup_local(&poolkey);
350}
351
352int ASYNC_init_thread(size_t max_size, size_t init_size)
353{
354    async_pool *pool;
355    size_t curr_size = 0;
356
357    if (init_size > max_size) {
358        ERR_raise(ERR_LIB_ASYNC, ASYNC_R_INVALID_POOL_SIZE);
359        return 0;
360    }
361
362    if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
363        return 0;
364
365    if (!ossl_init_thread_start(NULL, NULL, async_delete_thread_state))
366        return 0;
367
368    pool = OPENSSL_zalloc(sizeof(*pool));
369    if (pool == NULL) {
370        ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
371        return 0;
372    }
373
374    pool->jobs = sk_ASYNC_JOB_new_reserve(NULL, init_size);
375    if (pool->jobs == NULL) {
376        ERR_raise(ERR_LIB_ASYNC, ERR_R_MALLOC_FAILURE);
377        OPENSSL_free(pool);
378        return 0;
379    }
380
381    pool->max_size = max_size;
382
383    /* Pre-create jobs as required */
384    while (init_size--) {
385        ASYNC_JOB *job;
386        job = async_job_new();
387        if (job == NULL || !async_fibre_makecontext(&job->fibrectx)) {
388            /*
389             * Not actually fatal because we already created the pool, just
390             * skip creation of any more jobs
391             */
392            async_job_free(job);
393            break;
394        }
395        job->funcargs = NULL;
396        sk_ASYNC_JOB_push(pool->jobs, job); /* Cannot fail due to reserve */
397        curr_size++;
398    }
399    pool->curr_size = curr_size;
400    if (!CRYPTO_THREAD_set_local(&poolkey, pool)) {
401        ERR_raise(ERR_LIB_ASYNC, ASYNC_R_FAILED_TO_SET_POOL);
402        goto err;
403    }
404
405    return 1;
406err:
407    async_empty_pool(pool);
408    sk_ASYNC_JOB_free(pool->jobs);
409    OPENSSL_free(pool);
410    return 0;
411}
412
413static void async_delete_thread_state(void *arg)
414{
415    async_pool *pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
416
417    if (pool != NULL) {
418        async_empty_pool(pool);
419        sk_ASYNC_JOB_free(pool->jobs);
420        OPENSSL_free(pool);
421        CRYPTO_THREAD_set_local(&poolkey, NULL);
422    }
423    async_local_cleanup();
424    async_ctx_free();
425}
426
427void ASYNC_cleanup_thread(void)
428{
429    if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
430        return;
431
432    async_delete_thread_state(NULL);
433}
434
435ASYNC_JOB *ASYNC_get_current_job(void)
436{
437    async_ctx *ctx;
438
439    if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
440        return NULL;
441
442    ctx = async_get_ctx();
443    if (ctx == NULL)
444        return NULL;
445
446    return ctx->currjob;
447}
448
449ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job)
450{
451    return job->waitctx;
452}
453
454void ASYNC_block_pause(void)
455{
456    async_ctx *ctx;
457
458    if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
459        return;
460
461    ctx = async_get_ctx();
462    if (ctx == NULL || ctx->currjob == NULL) {
463        /*
464         * We're not in a job anyway so ignore this
465         */
466        return;
467    }
468    ctx->blocked++;
469}
470
471void ASYNC_unblock_pause(void)
472{
473    async_ctx *ctx;
474
475    if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
476        return;
477
478    ctx = async_get_ctx();
479    if (ctx == NULL || ctx->currjob == NULL) {
480        /*
481         * We're not in a job anyway so ignore this
482         */
483        return;
484    }
485    if (ctx->blocked > 0)
486        ctx->blocked--;
487}
488