From b8891fa1a4248e55ac4cd4c87af65193f609d1eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 15 Oct 2026 09:51:56 +0200 Subject: [PATCH] Rework the static-memory heap hint fix Heap hints are inherited from the CTX, and switching a static-memory SSL to a CTX with a different heap is outside that model. Instead of freeing through a stack-local hint that carries the creating pool, wolfSSL_set_SSL_CTX() now refuses a CTX whose heap differs from the current one. ssl->ctx->heap is then always the heap the SSL was created from, so wolfSSL_free(), FreeSSL() and FreeSSL_StaticMemory() go back to freeing through it. Releasing the SSL heap hint last in wolfSSL_ResourceFree() stays, as the DTLS 1.3, QUIC and peerSigSpec cleanups still free through ssl->heap. FreeSSL_StaticMemory() now clears ssl_hint->stats before freeing the stats block. When wolfSSL_new() fails before SetSSL_CTX(), ssl->ctx is NULL and the stats block and the hint are freed through ssl->heap itself, so wolfSSL_Free() wrote the connection statistics into the freed stats block on both frees. test_wolfSSL_set_SSL_CTX_static_heap checks that a switch to a second pool or to a CTX without a heap, and from one to a pool, is refused and leaves the SSL on its original CTX, while a switch between two CTXs on the same heap still succeeds. --- src/internal.c | 31 +++++++----------------- src/ssl.c | 13 +++++++++- tests/api.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/src/internal.c b/src/internal.c index 2fd14b48b0..7b67b03680 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10240,16 +10240,12 @@ static void FreeSSL_StaticMemory(WOLFSSL* ssl) /* avoid dereferencing a test value */ if (ssl->heap != (void*)WOLFSSL_HEAP_TEST) { #endif + void* heap = ssl->ctx ? ssl->ctx->heap : ssl->heap; + #ifndef WOLFSSL_STATIC_MEMORY_LEAN WOLFSSL_HEAP_HINT* ssl_hint = (WOLFSSL_HEAP_HINT*)ssl->heap; - WOLFSSL_HEAP_HINT poolHint; - #ifndef WOLFSSL_STATIC_MEMORY_LEAN WOLFSSL_HEAP* ctx_heap; - #endif + WOLFSSL_MEM_CONN_STATS* stats; - /* ssl->ctx may have been swapped, so free into the creating pool */ - XMEMSET(&poolHint, 0, sizeof(poolHint)); - poolHint.memory = ssl_hint->memory; - #ifndef WOLFSSL_STATIC_MEMORY_LEAN ctx_heap = ssl_hint->memory; #ifndef SINGLE_THREADED if (wc_LockMutex(&(ctx_heap->memory_mutex)) != 0) { @@ -10274,10 +10270,13 @@ static void FreeSSL_StaticMemory(WOLFSSL* ssl) /* check if tracking stats */ if (ctx_heap->flag & WOLFMEM_TRACK_STATS) { - XFREE(ssl_hint->stats, &poolHint, DYNAMIC_TYPE_SSL); + /* with ssl->ctx NULL, heap is ssl->heap and XFREE updates stats */ + stats = ssl_hint->stats; + ssl_hint->stats = NULL; + XFREE(stats, heap, DYNAMIC_TYPE_SSL); } #endif /* !WOLFSSL_STATIC_MEMORY_LEAN */ - XFREE(ssl->heap, &poolHint, DYNAMIC_TYPE_SSL); + XFREE(ssl->heap, heap, DYNAMIC_TYPE_SSL); #ifdef WOLFSSL_HEAP_TEST } #endif @@ -10890,20 +10889,6 @@ void FreeHandshakeResources(WOLFSSL* ssl) void FreeSSL(WOLFSSL* ssl, void* heap) { WOLFSSL_CTX* ctx = ssl->ctx; -#ifdef WOLFSSL_STATIC_MEMORY - WOLFSSL_HEAP_HINT poolHint; - - /* wolfSSL_ResourceFree() frees ssl->heap, so keep only its pool */ - if (heap != NULL && heap == ssl->heap - #ifdef WOLFSSL_HEAP_TEST - && heap != (void*)WOLFSSL_HEAP_TEST - #endif - ) { - XMEMSET(&poolHint, 0, sizeof(poolHint)); - poolHint.memory = ((WOLFSSL_HEAP_HINT*)heap)->memory; - heap = &poolHint; - } -#endif wolfSSL_ResourceFree(ssl); #ifdef WOLFSSL_CHECK_MEM_ZERO wc_MemZero_Check(ssl, sizeof(*ssl)); diff --git a/src/ssl.c b/src/ssl.c index 47ca3f2ad8..b285e84eae 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -800,11 +800,15 @@ WOLFSSL* wolfSSL_new(WOLFSSL_CTX* ctx) WOLFSSL_ABI void wolfSSL_free(WOLFSSL* ssl) { + void *heap = NULL; WOLFSSL_ENTER("wolfSSL_free"); if (ssl) { WOLFSSL_MSG_EX("Free SSL: %p", (wc_ptr_t)ssl); - FreeSSL(ssl, ssl->heap); + if (ssl->ctx != NULL) { + heap = ssl->ctx->heap; + } + FreeSSL(ssl, heap); } else { WOLFSSL_MSG("Free SSL: wolfSSL_free already null"); @@ -8996,6 +9000,13 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx) return NULL; if (ssl->ctx == ctx) return ssl->ctx; +#ifdef WOLFSSL_STATIC_MEMORY + /* the SSL and its heap hint are freed through ssl->ctx->heap */ + if (ssl->ctx != NULL && ssl->ctx->heap != ctx->heap) { + WOLFSSL_MSG("Cannot switch to a CTX with a different heap"); + return NULL; + } +#endif /* suites, allocated and derived under the CTX mutex as this CTX may be * shared with other threads. Done before taking a reference below so that diff --git a/tests/api.c b/tests/api.c index b67b4e349a..a6cbe46662 100644 --- a/tests/api.c +++ b/tests/api.c @@ -38255,6 +38255,70 @@ static int test_wolfSSL_CTX_StaticMemory(void) return EXPECT_RESULT(); } +/* An SSL may only switch to a CTX with the same static-memory heap. */ +static int test_wolfSSL_set_SSL_CTX_static_heap(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFCRYPT_ONLY) && \ + (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \ + !defined(NO_WOLFSSL_CLIENT) + byte memA[TEST_TLS_STATIC_MEMSZ]; + byte memB[TEST_TLS_STATIC_MEMSZ]; + WOLFSSL_HEAP_HINT* heapA = NULL; + WOLFSSL_HEAP_HINT* heapB = NULL; + WOLFSSL_CTX* ctxA = NULL; + WOLFSSL_CTX* ctxA2 = NULL; + WOLFSSL_CTX* ctxB = NULL; +#ifndef WOLFSSL_NO_MALLOC + WOLFSSL_CTX* ctxNoHeap = NULL; +#endif + WOLFSSL* ssl = NULL; + + ExpectIntEQ(wc_LoadStaticMemory(&heapA, memA, sizeof(memA), 0, 1), 0); + ExpectIntEQ(wc_LoadStaticMemory(&heapB, memB, sizeof(memB), 0, 1), 0); + ExpectNotNull(ctxA = wolfSSL_CTX_new_ex(wolfSSLv23_client_method_ex(heapA), + heapA)); + ExpectNotNull(ctxA2 = wolfSSL_CTX_new_ex( + wolfSSLv23_client_method_ex(heapA), heapA)); + ExpectNotNull(ctxB = wolfSSL_CTX_new_ex(wolfSSLv23_client_method_ex(heapB), + heapB)); + + ExpectNotNull(ssl = wolfSSL_new(ctxA)); + ExpectNull(wolfSSL_set_SSL_CTX(ssl, ctxB)); + ExpectPtrEq(wolfSSL_get_SSL_CTX(ssl), ctxA); + wolfSSL_free(ssl); + ssl = NULL; + +#ifndef WOLFSSL_NO_MALLOC + ExpectNotNull(ctxNoHeap = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctxA)); + ExpectNull(wolfSSL_set_SSL_CTX(ssl, ctxNoHeap)); + ExpectPtrEq(wolfSSL_get_SSL_CTX(ssl), ctxA); + wolfSSL_free(ssl); + ssl = NULL; + + ExpectNotNull(ssl = wolfSSL_new(ctxNoHeap)); + ExpectNull(wolfSSL_set_SSL_CTX(ssl, ctxA)); + ExpectPtrEq(wolfSSL_get_SSL_CTX(ssl), ctxNoHeap); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctxNoHeap); +#endif + + ExpectNotNull(ssl = wolfSSL_new(ctxA)); + ExpectPtrEq(wolfSSL_set_SSL_CTX(ssl, ctxA2), ctxA2); + ExpectPtrEq(wolfSSL_get_SSL_CTX(ssl), ctxA2); + wolfSSL_free(ssl); + + wolfSSL_CTX_free(ctxA); + wolfSSL_CTX_free(ctxA2); + wolfSSL_CTX_free(ctxB); + wc_UnloadStaticMemory(heapA); + wc_UnloadStaticMemory(heapB); +#endif + return EXPECT_RESULT(); +} + static int test_openssl_FIPS_drbg(void) { EXPECT_DECLS; @@ -45798,6 +45862,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wc_CryptoCb_Hmac_Find), /* Can't memory test as client/server hangs. */ TEST_DECL(test_wolfSSL_CTX_StaticMemory), + TEST_DECL(test_wolfSSL_set_SSL_CTX_static_heap), #if !defined(NO_FILESYSTEM) && \ defined(WOLFSSL_DTLS) && !defined(WOLFSSL_NO_TLS12) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)