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