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 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 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; 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 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 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 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 150 void 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 177 int 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 284 err: 285 async_release_job(ctx->currjob); 286 ctx->currjob = NULL; 287 *job = NULL; 288 return ASYNC_ERR; 289 } 290 291 int 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 320 static 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 333 int 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 346 void async_deinit(void) 347 { 348 CRYPTO_THREAD_cleanup_local(&ctxkey); 349 CRYPTO_THREAD_cleanup_local(&poolkey); 350 } 351 352 int 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; 406 err: 407 async_empty_pool(pool); 408 sk_ASYNC_JOB_free(pool->jobs); 409 OPENSSL_free(pool); 410 return 0; 411 } 412 413 static 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 427 void 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 435 ASYNC_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 449 ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job) 450 { 451 return job->waitctx; 452 } 453 454 void 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 471 void 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