Commit 31659fe326 for openssl.org

commit 31659fe32673a6bd66abf3f8a7d803e81c6ffeed
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date:   Mon Nov 24 17:05:26 2025 +0100

    Introduce OPENSSL_ATEXIT_CLEANUP env. variable.

    libcrypto does not arm OPENSSL_cleanup() function as atexit(3) handler by default.
    If application/user wants libcrypto to install OPENSSL_cleanup() as atexit handler,
    then OPENSSL_ATEXIT_CLEANUP env. variable must be set.

    If platform's libc does not provide atexit(3), then OPENSSL_ATEXIT_CLEANUP has no effect.

    The OPENSSL_atexit() is wrapper of atexit(3) provided by libc now.

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Matt Caswell <matt@openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/29385)

diff --git a/CHANGES.md b/CHANGES.md
index 0ca5ba5398..f17d094732 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -36,11 +36,19 @@ OpenSSL 4.0

    *Norbert Pocs*

+ * libcrypto no longer arms OPENSSL_cleanup() as atexit(3) handler by default.
+   Memory leak detectors now report there is allocated and reachable memory
+   at application exit. To avoid such leak detection the application must
+   call OPENSSL_cleanup() before main() exits.
+
+   *Alexandr Nedvedicky*
+
  * The crypto-mdebug-backtrace configuration option has been entirely removed.
    The option has been a no-op since 1.0.2.

    *Neil Horman*

+
  * Removed extra leading '00:' when printing key data such as an RSA modulus
    in hexadecimal format where the first (most significant) byte is >= 0x80.
    This had been added artificially to resemble ASN.1 DER encoding internals.
diff --git a/apps/fipsinstall.c b/apps/fipsinstall.c
index ea54a00cff..10114cb1d8 100644
--- a/apps/fipsinstall.c
+++ b/apps/fipsinstall.c
@@ -967,6 +967,7 @@ cleanup:
     EVP_MAC_CTX_free(ctx);
     OPENSSL_free(read_buffer);
     free_config_and_unload(conf);
+    OPENSSL_cleanup();
     return ret;
 }

diff --git a/apps/openssl.c b/apps/openssl.c
index 61623086f7..2d587e2249 100644
--- a/apps/openssl.c
+++ b/apps/openssl.c
@@ -372,6 +372,7 @@ end:
 #ifndef OPENSSL_NO_SECURE_MEMORY
     CRYPTO_secure_malloc_done();
 #endif
+    OPENSSL_cleanup();
     EXIT(ret);
 }

diff --git a/crypto/init.c b/crypto/init.c
index 457ce4ca69..3049c48a5e 100644
--- a/crypto/init.c
+++ b/crypto/init.c
@@ -35,13 +35,6 @@
 static int stopped = 0;
 static uint64_t optsdone = 0;

-typedef struct ossl_init_stop_st OPENSSL_INIT_STOP;
-struct ossl_init_stop_st {
-    void (*handler)(void);
-    OPENSSL_INIT_STOP *next;
-};
-
-static OPENSSL_INIT_STOP *stop_handlers = NULL;
 /* Guards access to the optsdone variable on platforms without atomics */
 static CRYPTO_RWLOCK *optsdone_lock = NULL;
 /* Guards simultaneous INIT_LOAD_CONFIG calls with non-NULL settings */
@@ -84,45 +77,6 @@ err:
     return 0;
 }

-static CRYPTO_ONCE register_atexit = CRYPTO_ONCE_STATIC_INIT;
-#if !defined(OPENSSL_SYS_UEFI) && defined(_WIN32)
-static int win32atexit(void)
-{
-    OPENSSL_cleanup();
-    return 0;
-}
-#endif
-
-DEFINE_RUN_ONCE_STATIC(ossl_init_register_atexit)
-{
-#ifndef OPENSSL_NO_ATEXIT
-#ifdef OPENSSL_INIT_DEBUG
-    fprintf(stderr, "OPENSSL_INIT: ossl_init_register_atexit()\n");
-#endif
-#ifndef OPENSSL_SYS_UEFI
-#if defined(_WIN32) && !defined(__BORLANDC__)
-    /* We use _onexit() in preference because it gets called on DLL unload */
-    if (_onexit(win32atexit) == NULL)
-        return 0;
-#else
-    if (atexit(OPENSSL_cleanup) != 0)
-        return 0;
-#endif
-#endif
-#endif
-
-    return 1;
-}
-
-DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_register_atexit,
-    ossl_init_register_atexit)
-{
-#ifdef OPENSSL_INIT_DEBUG
-    fprintf(stderr, "OPENSSL_INIT: ossl_init_no_register_atexit ok!\n");
-#endif
-    /* Do nothing in this case */
-    return 1;
-}

 static CRYPTO_ONCE load_crypto_nodelete = CRYPTO_ONCE_STATIC_INIT;
 DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_nodelete)
@@ -150,7 +104,7 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_nodelete)
 #elif !defined(DSO_NONE)
     /*
      * Deliberately leak a reference to ourselves. This will force the library
-     * to remain loaded until the atexit() handler is run at process exit.
+     * to remain loaded until the OPENSSL_cleanup()  is called.
      */
     {
         DSO *dso;
@@ -308,8 +262,6 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_async)

 void OPENSSL_cleanup(void)
 {
-    OPENSSL_INIT_STOP *currhandler, *lasthandler;
-
     /*
      * At some point we should consider looking at this function with a view to
      * moving most/all of this into onfree handlers in OSSL_LIB_CTX.
@@ -319,7 +271,7 @@ void OPENSSL_cleanup(void)
     if (!base_inited)
         return;

-    /* Might be explicitly called and also by atexit */
+    /* Might be explicitly called a*/
     if (stopped)
         return;
     stopped = 1;
@@ -330,15 +282,6 @@ void OPENSSL_cleanup(void)
      */
     OPENSSL_thread_stop();

-    currhandler = stop_handlers;
-    while (currhandler != NULL) {
-        currhandler->handler();
-        lasthandler = currhandler;
-        currhandler = currhandler->next;
-        OPENSSL_free(lasthandler);
-    }
-    stop_handlers = NULL;
-
     CRYPTO_THREAD_lock_free(optsdone_lock);
     optsdone_lock = NULL;
     CRYPTO_THREAD_lock_free(init_lock);
@@ -486,20 +429,6 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
             return 1;
     }

-    /*
-     * Now we don't always set up exit handlers, the INIT_BASE_ONLY calls
-     * should not have the side-effect of setting up exit handlers, and
-     * therefore, this code block is below the INIT_BASE_ONLY-conditioned early
-     * return above.
-     */
-    if ((opts & OPENSSL_INIT_NO_ATEXIT) != 0) {
-        if (!RUN_ONCE_ALT(&register_atexit, ossl_init_no_register_atexit,
-                ossl_init_register_atexit))
-            return 0;
-    } else if (!RUN_ONCE(&register_atexit, ossl_init_register_atexit)) {
-        return 0;
-    }
-
     if (!RUN_ONCE(&load_crypto_nodelete, ossl_init_load_crypto_nodelete))
         return 0;

@@ -586,64 +515,9 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)

 int OPENSSL_atexit(void (*handler)(void))
 {
-    OPENSSL_INIT_STOP *newhand;
-
-#if !defined(OPENSSL_USE_NODELETE) \
-    && !defined(OPENSSL_NO_PINSHARED)
-    {
-#if defined(DSO_WIN32) && !defined(_WIN32_WCE)
-        HMODULE handle = NULL;
-        BOOL ret;
-        union {
-            void *sym;
-            void (*func)(void);
-        } handlersym;
-
-        handlersym.func = handler;
-
-        /*
-         * We don't use the DSO route for WIN32 because there is a better
-         * way
-         */
-        ret = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
-                | GET_MODULE_HANDLE_EX_FLAG_PIN,
-            handlersym.sym, &handle);
-
-        if (!ret)
-            return 0;
-#elif !defined(DSO_NONE)
-        /*
-         * Deliberately leak a reference to the handler. This will force the
-         * library/code containing the handler to remain loaded until we run the
-         * atexit handler. If -znodelete has been used then this is
-         * unnecessary.
-         */
-        DSO *dso = NULL;
-        union {
-            void *sym;
-            void (*func)(void);
-        } handlersym;
-
-        handlersym.func = handler;
-
-        ERR_set_mark();
-        dso = DSO_dsobyaddr(handlersym.sym, DSO_FLAG_NO_UNLOAD_ON_FREE);
-        /* See same code above in ossl_init_base() for an explanation. */
-        OSSL_TRACE1(INIT,
-            "atexit: obtained DSO reference? %s\n",
-            (dso == NULL ? "No!" : "Yes."));
-        DSO_free(dso);
-        ERR_pop_to_mark();
-#endif
-    }
+#if defined(__TANDEM)
+    return 0;
+#else
+    return atexit(handler) == 0;
 #endif
-
-    if ((newhand = OPENSSL_malloc(sizeof(*newhand))) == NULL)
-        return 0;
-
-    newhand->handler = handler;
-    newhand->next = stop_handlers;
-    stop_handlers = newhand;
-
-    return 1;
 }
diff --git a/crypto/initthread.c b/crypto/initthread.c
index 1e1b9e69db..d408ea9ca1 100644
--- a/crypto/initthread.c
+++ b/crypto/initthread.c
@@ -326,7 +326,12 @@ err:

 void ossl_thread_event_ctx_free(OSSL_LIB_CTX *ctx)
 {
+    THREAD_EVENT_HANDLER **hands;
+
+    hands = (THREAD_EVENT_HANDLER **)CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_TEVENT_KEY, ctx);
     CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_TEVENT_KEY, ctx, NULL);
+
+    OPENSSL_free(hands);
 }

 static void ossl_arg_thread_stop(void *arg)
diff --git a/doc/man3/OPENSSL_init_crypto.pod b/doc/man3/OPENSSL_init_crypto.pod
index 3ef6aba822..0740c219d2 100644
--- a/doc/man3/OPENSSL_init_crypto.pod
+++ b/doc/man3/OPENSSL_init_crypto.pod
@@ -124,13 +124,6 @@ sub-library (see L<ASYNC_start_job(3)>). This is a default option.
 With this option the library will register its fork handlers.
 See OPENSSL_fork_prepare(3) for details.

-=item OPENSSL_INIT_NO_ATEXIT
-
-By default OpenSSL will attempt to clean itself up when the process exits via an
-"atexit" handler. Using this option suppresses that behaviour. This means that
-the application will have to clean up OpenSSL explicitly using
-OPENSSL_cleanup().
-
 =back

 Multiple options may be combined together in a single call to
@@ -158,11 +151,7 @@ OpenSSL error strings will not be available, only an error code. This code can
 be put through the openssl errstr command line application to produce a human
 readable error (see L<openssl-errstr(1)>).

-The OPENSSL_atexit() function enables the registration of a
-function to be called during OPENSSL_cleanup(). Stop handlers are
-called after deinitialisation of resources local to a thread, but before other
-process wide resources are freed. In the event that multiple stop handlers are
-registered, no guarantees are made about the order of execution.
+The OPENSSL_atexit() is a wrapper on atexit(3) provided by platform's libc.

 The OPENSSL_thread_stop_ex() function deallocates resources associated
 with the current thread for the given OSSL_LIB_CTX B<ctx>. The B<ctx> parameter
diff --git a/include/openssl/crypto.h.in b/include/openssl/crypto.h.in
index c9dc3706f3..9341985026 100644
--- a/include/openssl/crypto.h.in
+++ b/include/openssl/crypto.h.in
@@ -490,8 +490,8 @@ int CRYPTO_memcmp(const void *in_a, const void *in_b, size_t len);
 /* FREE:                                     0x00010000L */
 #define OPENSSL_INIT_ATFORK 0x00020000L
 /* OPENSSL_INIT_BASE_ONLY                    0x00040000L */
-#define OPENSSL_INIT_NO_ATEXIT 0x00080000L
 /* OPENSSL_INIT flag range 0x03f00000 reserved for OPENSSL_init_ssl() */
+/* FREE: 0x00080000L */
 /* FREE: 0x04000000L */
 /* FREE: 0x08000000L */
 /* FREE: 0x10000000L */
diff --git a/test/defltfips_test.c b/test/defltfips_test.c
index 16d834b020..b7e85211c4 100644
--- a/test/defltfips_test.c
+++ b/test/defltfips_test.c
@@ -8,6 +8,7 @@
  */

 #include <string.h>
+#include <openssl/crypto.h>
 #include <openssl/evp.h>
 #include <openssl/provider.h>
 #include "testutil.h"
@@ -68,6 +69,11 @@ static int test_is_fips_enabled(void)
     return 1;
 }

+void cleanup_tests(void)
+{
+    OPENSSL_cleanup();
+}
+
 int setup_tests(void)
 {
     size_t argc;
diff --git a/test/endecode_test.c b/test/endecode_test.c
index 6081ef5d0b..58fd12473b 100644
--- a/test/endecode_test.c
+++ b/test/endecode_test.c
@@ -9,6 +9,7 @@

 #include <string.h>
 #include <openssl/core_dispatch.h>
+#include <openssl/crypto.h>
 #include <openssl/evp.h>
 #include <openssl/pem.h>
 #include <openssl/rsa.h>
@@ -1767,4 +1768,6 @@ void cleanup_tests(void)
     OSSL_PROVIDER_unload(keyprov);
     OSSL_LIB_CTX_free(testctx);
     OSSL_LIB_CTX_free(keyctx);
+
+    OPENSSL_cleanup();
 }
diff --git a/test/rand_test.c b/test/rand_test.c
index 0fbd89542f..0ac097cb34 100644
--- a/test/rand_test.c
+++ b/test/rand_test.c
@@ -7,6 +7,7 @@
  * https://www.openssl.org/source/license.html
  */

+#include <openssl/crypto.h>
 #include <openssl/evp.h>
 #include <openssl/rand.h>
 #include <openssl/bio.h>
@@ -275,6 +276,11 @@ err:
     return res;
 }

+void cleanup_tests(void)
+{
+    OPENSSL_cleanup();
+}
+
 int setup_tests(void)
 {
     if (!test_skip_common_options()) {
diff --git a/test/recipes/90-test_shlibload.t b/test/recipes/90-test_shlibload.t
index 67afff607e..7a717dd92a 100644
--- a/test/recipes/90-test_shlibload.t
+++ b/test/recipes/90-test_shlibload.t
@@ -25,7 +25,7 @@ plan skip_all => "Test only supported in a dso build" if disabled("dso");
 plan skip_all => "Test is disabled in an address sanitizer build" unless disabled("asan");
 plan skip_all => "Test is disabled in no-atexit build" if disabled("atexit");

-plan tests => 10;
+plan tests => 8;

 my $libcrypto = platform->sharedlib('libcrypto');
 my $libssl = platform->sharedlib('libssl');
@@ -55,12 +55,6 @@ ok(run(test(["shlibloadtest", "-dso_ref", $libcrypto, $libssl, $atexit_outfile])
    "running shlibloadtest -dso_ref $atexit_outfile");
 ok(check_atexit($atexit_outfile));

-$atexit_outfile = 'atexit-noatexit.txt';
-1 while unlink $atexit_outfile;
-ok(run(test(["shlibloadtest", "-no_atexit", $libcrypto, $libssl, $atexit_outfile])),
-   "running shlibloadtest -no_atexit $atexit_outfile");
-ok(!check_atexit($atexit_outfile));
-
 sub check_atexit {
     my $filename = shift;

diff --git a/test/shlibloadtest.c b/test/shlibloadtest.c
index 034780ea55..5fa37415a5 100644
--- a/test/shlibloadtest.c
+++ b/test/shlibloadtest.c
@@ -34,7 +34,6 @@ typedef enum test_types_en {
     SSL_FIRST,
     JUST_CRYPTO,
     DSO_REFTEST,
-    NO_ATEXIT
 } TEST_TYPE;

 static TEST_TYPE test_type;
@@ -80,7 +79,6 @@ static int test_lib(void)
     switch (test_type) {
     case JUST_CRYPTO:
     case DSO_REFTEST:
-    case NO_ATEXIT:
     case CRYPTO_FIRST:
         if (!sd_load(path_crypto, &cryptolib, SD_SHLIB)) {
             fprintf(stderr, "Failed to load libcrypto\n");
@@ -104,23 +102,8 @@ static int test_lib(void)
         break;
     }

-    if (test_type == NO_ATEXIT) {
-        OPENSSL_init_crypto_t myOPENSSL_init_crypto;
-
-        if (!sd_sym(cryptolib, "OPENSSL_init_crypto", &symbols[0].sym)) {
-            fprintf(stderr, "Failed to load OPENSSL_init_crypto symbol\n");
-            goto end;
-        }
-        myOPENSSL_init_crypto = (OPENSSL_init_crypto_t)symbols[0].func;
-        if (!myOPENSSL_init_crypto(OPENSSL_INIT_NO_ATEXIT, NULL)) {
-            fprintf(stderr, "Failed to initialise libcrypto\n");
-            goto end;
-        }
-    }
-
     if (test_type != JUST_CRYPTO
-        && test_type != DSO_REFTEST
-        && test_type != NO_ATEXIT) {
+        && test_type != DSO_REFTEST) {
         if (!sd_sym(ssllib, "TLS_method", &symbols[0].sym)
             || !sd_sym(ssllib, "SSL_CTX_new", &symbols[1].sym)
             || !sd_sym(ssllib, "SSL_CTX_free", &symbols[2].sym)) {
@@ -228,7 +211,7 @@ static int test_lib(void)
      * running atexit() on so unload. If not we might crash. We know this is
      * true on linux since glibc 2.2.3
      */
-    if (test_type != NO_ATEXIT && atexit_handler_done != 1) {
+    if (atexit_handler_done != 1) {
         fprintf(stderr, "atexit() handler did not run\n");
         goto end;
     }
@@ -269,8 +252,6 @@ int main(int argc, char *argv[])
         test_type = JUST_CRYPTO;
     } else if (strcmp(p, "-dso_ref") == 0) {
         test_type = DSO_REFTEST;
-    } else if (strcmp(p, "-no_atexit") == 0) {
-        test_type = NO_ATEXIT;
     } else {
         fprintf(stderr, "Unrecognised argument\n");
         return 1;