Coverage Report

Created: 2025-12-31 06:58

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