From 0458577b338e70c60a66ea93560f953dfb93fb5b Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Tue, 8 Sep 2026 17:14:07 +0900 Subject: [PATCH] ssl_api_ext: lock ticket key context in the public key APIs - wolfSSL_CTX_get_tlsext_ticket_keys() and wolfSSL_CTX_set_tlsext_ticket_keys() hold ticketKeyCtx.mutex across the name, key and expirary copy and return WOLFSSL_FAILURE when the lock fails, under !SINGLE_THREADED. The @return line of each names that failure. - The #if around both functions in src/ssl_api_ext.c and around their prototypes in wolfssl/ssl.h adds !defined(NO_TLS). - tests/api/test_session.c gains test_wolfSSL_ticket_keys_threaded(), a writer thread rotating two key blobs against a reader asserting each get returns one blob whole and that both blobs were seen. Declared and registered in tests/api/test_session.h. Issue: F-13440 --- src/ssl_api_ext.c | 28 +++++-- tests/api/test_session.c | 173 +++++++++++++++++++++++++++++++++++++++ tests/api/test_session.h | 2 + wolfssl/ssl.h | 2 +- 4 files changed, 199 insertions(+), 6 deletions(-) diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 2be0da03757..a8adcec0cfd 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -2287,15 +2287,15 @@ int wolfSSL_CTX_set_tlsext_ticket_key_cb(WOLFSSL_CTX *ctx, ticketCompatCb cb) OPENSSL_EXTRA || HAVE_LIGHTY */ #if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ - !defined(NO_WOLFSSL_SERVER) + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) /* Serialize the session ticket encryption keys. * * @param [in] ctx SSL/TLS context object. * @param [out] keys Buffer to hold session ticket keys. * @param [in] keylen Length of buffer. * @return WOLFSSL_SUCCESS on success. - * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL or keylen is not the - * correct length. + * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL, keylen is not the + * correct length or the key context mutex cannot be locked. */ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, unsigned char *keys, int keylen) @@ -2306,6 +2306,12 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, (keylen != WOLFSSL_TICKET_KEYS_SZ)) { ret = WOLFSSL_FAILURE; } +#ifndef SINGLE_THREADED + else if (wc_LockMutex(&ctx->ticketKeyCtx.mutex) != 0) { + WOLFSSL_MSG("Couldn't lock key context mutex"); + ret = WOLFSSL_FAILURE; + } +#endif else { XMEMCPY(keys, ctx->ticketKeyCtx.name, WOLFSSL_TICKET_NAME_SZ); keys += WOLFSSL_TICKET_NAME_SZ; @@ -2316,6 +2322,9 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, c32toa(ctx->ticketKeyCtx.expirary[0], keys); keys += OPAQUE32_LEN; c32toa(ctx->ticketKeyCtx.expirary[1], keys); +#ifndef SINGLE_THREADED + wc_UnLockMutex(&ctx->ticketKeyCtx.mutex); +#endif } return ret; @@ -2327,8 +2336,8 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, * @param [in] keys_vp Session ticket keys. * @param [in] keylen Length of data. * @return WOLFSSL_SUCCESS on success. - * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL or keylen is not the - * correct length. + * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL, keylen is not the + * correct length or the key context mutex cannot be locked. */ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, const void *keys_vp, int keylen) @@ -2340,6 +2349,12 @@ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, (keylen != WOLFSSL_TICKET_KEYS_SZ)) { ret = WOLFSSL_FAILURE; } +#ifndef SINGLE_THREADED + else if (wc_LockMutex(&ctx->ticketKeyCtx.mutex) != 0) { + WOLFSSL_MSG("Couldn't lock key context mutex"); + ret = WOLFSSL_FAILURE; + } +#endif else { XMEMCPY(ctx->ticketKeyCtx.name, keys, WOLFSSL_TICKET_NAME_SZ); keys += WOLFSSL_TICKET_NAME_SZ; @@ -2350,6 +2365,9 @@ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, ato32(keys, &ctx->ticketKeyCtx.expirary[0]); keys += OPAQUE32_LEN; ato32(keys, &ctx->ticketKeyCtx.expirary[1]); +#ifndef SINGLE_THREADED + wc_UnLockMutex(&ctx->ticketKeyCtx.mutex); +#endif } return ret; diff --git a/tests/api/test_session.c b/tests/api/test_session.c index 3c08f32bef1..86b28def154 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -1525,6 +1525,179 @@ int test_wolfSSL_ticket_keys(void) return EXPECT_RESULT(); } +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && !defined(SINGLE_THREADED) + +#define TEST_TICKET_KEYS_ROUNDS 20000 +#define TEST_TICKET_KEYS_WRITER_MAX 1000000 +#define TEST_TICKET_KEYS_BURST 256 +#define TEST_TICKET_KEYS_WAIT_TRIES 200 +#define TEST_TICKET_KEYS_WAIT_MS 1 + +static WOLFSSL_CTX* ticket_keys_ctx = NULL; +static byte ticket_keys_a[WOLFSSL_TICKET_KEYS_SZ]; +static byte ticket_keys_b[WOLFSSL_TICKET_KEYS_SZ]; +static int ticket_keys_set_err = 0; + +/* Guards the fields below, which both threads touch while running. */ +static wolfSSL_Mutex ticket_keys_lock; +static int ticket_keys_writer_started = 0; +static int ticket_keys_reader_done = 0; + +static THREAD_RETURN WOLFSSL_THREAD test_ticket_keys_writer(void* args) +{ + int done = 0; + int i; + long rounds = 0; + + (void)args; + + if (wc_LockMutex(&ticket_keys_lock) == 0) { + ticket_keys_writer_started = 1; + wc_UnLockMutex(&ticket_keys_lock); + } + + /* Keep rotating until the reader is done so the loops overlap. The + * rotations run in bursts to keep the bookkeeping out of the loop. */ + while ((!done) && (rounds < TEST_TICKET_KEYS_WRITER_MAX)) { + for (i = 0; i < TEST_TICKET_KEYS_BURST; i++) { + if (wolfSSL_CTX_set_tlsext_ticket_keys(ticket_keys_ctx, + ticket_keys_a, WOLFSSL_TICKET_KEYS_SZ) + != WOLFSSL_SUCCESS) { + ticket_keys_set_err = 1; + } + if (wolfSSL_CTX_set_tlsext_ticket_keys(ticket_keys_ctx, + ticket_keys_b, WOLFSSL_TICKET_KEYS_SZ) + != WOLFSSL_SUCCESS) { + ticket_keys_set_err = 1; + } + } + rounds += TEST_TICKET_KEYS_BURST; + + if (wc_LockMutex(&ticket_keys_lock) == 0) { + done = ticket_keys_reader_done; + wc_UnLockMutex(&ticket_keys_lock); + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* Read the keys once and record which key set came back. */ +static int test_ticket_keys_read(int* sawA, int* sawB, int* mixed) +{ + byte keys[WOLFSSL_TICKET_KEYS_SZ]; + int ret = 0; + + if (wolfSSL_CTX_get_tlsext_ticket_keys(ticket_keys_ctx, keys, + WOLFSSL_TICKET_KEYS_SZ) != WOLFSSL_SUCCESS) { + ret = -1; + } + else if (XMEMCMP(keys, ticket_keys_a, sizeof(keys)) == 0) { + *sawA = 1; + } + else if (XMEMCMP(keys, ticket_keys_b, sizeof(keys)) == 0) { + *sawB = 1; + } + /* Every read returns one key set whole, never part of each. */ + else { + *mixed = 1; + } + + return ret; +} +#endif + +int test_wolfSSL_ticket_keys_threaded(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && !defined(SINGLE_THREADED) + THREAD_TYPE thread; + func_args args; + int lockInit = 0; + int started = 0; + int sawA = 0; + int sawB = 0; + int getErr = 0; + int mixed = 0; + int i; + + XMEMSET(&args, 0, sizeof(args)); + XMEMSET(ticket_keys_a, 0xa5, sizeof(ticket_keys_a)); + XMEMSET(ticket_keys_b, 0x5c, sizeof(ticket_keys_b)); + ticket_keys_writer_started = 0; + ticket_keys_reader_done = 0; + ticket_keys_set_err = 0; + + if (wc_InitMutex(&ticket_keys_lock) == 0) { + lockInit = 1; + } + ExpectIntEQ(lockInit, 1); + ExpectNotNull(ticket_keys_ctx = + wolfSSL_CTX_new(wolfSSLv23_server_method())); + ExpectIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(ticket_keys_ctx, + ticket_keys_a, WOLFSSL_TICKET_KEYS_SZ), WOLFSSL_SUCCESS); + + if ((lockInit == 1) && (ticket_keys_ctx != NULL)) { + start_thread(test_ticket_keys_writer, &args, &thread); + + /* Start the reads only once the writer is rotating keys. Sleep + * rather than spin so the writer gets scheduled on one core. */ + for (i = 0; (i < TEST_TICKET_KEYS_WAIT_TRIES) && (!started); i++) { + if (wc_LockMutex(&ticket_keys_lock) == 0) { + started = ticket_keys_writer_started; + wc_UnLockMutex(&ticket_keys_lock); + } + if (!started) { + XSLEEP_MS(TEST_TICKET_KEYS_WAIT_MS); + } + } + + for (i = 0; i < TEST_TICKET_KEYS_ROUNDS; i++) { + if (test_ticket_keys_read(&sawA, &sawB, &mixed) != 0) { + getErr = 1; + break; + } + if (mixed) { + break; + } + } + + /* Top up until both key sets have been seen, so a clean run + * above cannot be one where the loops never overlapped. */ + for (i = 0; (i < TEST_TICKET_KEYS_WAIT_TRIES) && (!getErr) && + (!mixed) && ((!sawA) || (!sawB)); i++) { + XSLEEP_MS(TEST_TICKET_KEYS_WAIT_MS); + if (test_ticket_keys_read(&sawA, &sawB, &mixed) != 0) { + getErr = 1; + } + } + + if (wc_LockMutex(&ticket_keys_lock) == 0) { + ticket_keys_reader_done = 1; + wc_UnLockMutex(&ticket_keys_lock); + } + join_thread(thread); + } + + ExpectIntEQ(getErr, 0); + ExpectIntEQ(mixed, 0); + ExpectIntEQ(ticket_keys_set_err, 0); + + ExpectIntEQ(started, 1); + ExpectIntEQ(sawA, 1); + ExpectIntEQ(sawB, 1); + + wolfSSL_CTX_free(ticket_keys_ctx); + ticket_keys_ctx = NULL; + if (lockInit == 1) { + wc_FreeMutex(&ticket_keys_lock); + } +#endif + return EXPECT_RESULT(); +} + /*----------------------------------------------------------------------------*/ /* SESSION ex_data new index */ /*----------------------------------------------------------------------------*/ diff --git a/tests/api/test_session.h b/tests/api/test_session.h index be3deb5f04f..51b7240dbd9 100644 --- a/tests/api/test_session.h +++ b/tests/api/test_session.h @@ -35,6 +35,7 @@ int test_wolfSSL_SESSION(void); int test_wolfSSL_SESSION_expire_downgrade(void); int test_wolfSSL_CTX_sess_set_remove_cb(void); int test_wolfSSL_ticket_keys(void); +int test_wolfSSL_ticket_keys_threaded(void); int test_wolfSSL_SESSION_get_ex_new_index(void); int test_wolfSSL_GetSessionAtIndex(void); @@ -50,6 +51,7 @@ int test_wolfSSL_GetSessionAtIndex(void); TEST_DECL_GROUP("session", test_wolfSSL_SESSION_expire_downgrade), \ TEST_DECL_GROUP("session", test_wolfSSL_CTX_sess_set_remove_cb), \ TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys), \ + TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys_threaded), \ TEST_DECL_GROUP("session", test_wolfSSL_SESSION_get_ex_new_index), \ TEST_DECL_GROUP("session", test_wolfSSL_GetSessionAtIndex) diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 30b0d2908fe..bc1de35add8 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -6163,7 +6163,7 @@ WOLFSSL_API int PEM_write_bio_WOLFSSL_X509(WOLFSSL_BIO *bio, OPENSSL_EXTRA || HAVE_LIGHTY */ #if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ - !defined(NO_WOLFSSL_SERVER) + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) WOLFSSL_API long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, unsigned char *keys, int keylen); WOLFSSL_API long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx,