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