From c8d148c1ea0b018964dc24cfce01ac0cf3e8940e Mon Sep 17 00:00:00 2001 From: Norm Brandinger Date: Mon, 9 Mar 2026 08:45:44 -0400 Subject: [PATCH] tls_openssl: fix per-thread state double-free across fork() Register a pthread_atfork prepare handler that calls OPENSSL_thread_stop() before each fork(). CRYPTO_set_mem_functions() routes all OpenSSL allocations to shared memory, but per-thread structures (ERR_STATE, DRBG) use thread-local storage pointers inherited across fork(). Without cleanup, child processes inherit a stale pointer to the parent per-thread state; if the parent frees or re-creates that state, the child next OpenSSL call triggers a double-free (detected by QM_MALLOC_DBG as SIGABRT). After OPENSSL_thread_stop() the thread-local pointer is NULL. Both parent and child lazily allocate fresh per-thread state on the next OpenSSL call. This complements the existing on_exit(_exit) handler which covers the same class of double-free at process exit time. --- modules/tls_openssl/openssl.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/modules/tls_openssl/openssl.c b/modules/tls_openssl/openssl.c index 522b6825852..e8aa979e5db 100644 --- a/modules/tls_openssl/openssl.c +++ b/modules/tls_openssl/openssl.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "../../dprint.h" #include "../../mem/shm_mem.h" @@ -162,6 +163,30 @@ static void openssl_on_exit(int status, void *param) } #endif +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +/* + * Clean up OpenSSL per-thread state (ERR_STATE, DRBG, etc.) in the parent + * process before fork(). CRYPTO_set_mem_functions() routes all OpenSSL + * allocations to shared memory, but per-thread structures use thread-local + * storage pointers that are inherited across fork(). Without this cleanup, + * child processes inherit a stale pointer to the parent's per-thread state + * in shared memory; if the parent frees or re-creates that state, the + * child's next OpenSSL call triggers a double-free (detected by + * QM_MALLOC_DBG as SIGABRT). + * + * After OPENSSL_thread_stop(), the thread-local pointer is NULL. Both + * parent and child lazily allocate fresh per-thread state on the next + * OpenSSL call. + * + * This complements the on_exit(_exit) workaround above, which prevents the + * same class of double-free at process *exit* time. + */ +static void openssl_pre_fork(void) +{ + OPENSSL_thread_stop(); +} +#endif + #if (OPENSSL_VERSION_NUMBER < 0x10100000L) static int check_for_krb(void) { @@ -297,6 +322,13 @@ static int mod_init(void) on_exit(openssl_on_exit, NULL); #endif +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (pthread_atfork(openssl_pre_fork, NULL, NULL) != 0) { + LM_ERR("failed to register atfork handler for OpenSSL cleanup\n"); + return -1; + } +#endif + return 0; }