Coverage Report

Created: 2025-12-10 06:24

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