Commit 7e56cab285 for openssl.org

commit 7e56cab285e208c500110767c7edfaec9b96155c
Author: Bob Beck <beck@openssl.org>
Date:   Thu Jan 22 12:22:34 2026 -0700

    Make OPENSSL_cleanup() G A

    (Your choice of G and A words)

    This installs a global destructor if we have destructor support.

    The global destructor does nothing and immediately returns under
    normal operation. If a global flag indicating that global cleanup
    is wanted, it does what OPENSSL_cleanup() used to do.

    OPENSSL_cleanup() is then modified to set the global flag indicating
    that global cleanup is wanted. At this point if we have destructor
    support, it immeditely returns. If we do not have destructor support,
    it manually calls the destructor function (meaning without destructor
    support it does exactly what it used to do).

    This ensures that if we have destructor support, the actions of an
    OPENSSL_cleanup() requested by an application will only happen
    after any subordinate library destructors which could call into
    OpenSSL functions have already run.

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    MergeDate: Thu Feb  5 19:19:17 2026
    (Merged from https://github.com/openssl/openssl/pull/29721)

diff --git a/CHANGES.md b/CHANGES.md
index b9a544a10c..1aef34dc1f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,16 @@ OpenSSL 4.0

 ### Changes between 3.6 and 4.0 [xx XXX xxxx]

+ * OPENSSL_cleanup() now runs in a global destructor, or not at all by default.
+
+   OpenSSL_cleanup() will no longer by default free global objects when run from
+   an application. Instead it sets a flag for a global destructor to do this after
+   the process exits, and after subordinate libraries using OpenSSL have run their
+   destructors. If destructor support is not available, OpenSSL_cleanup() will do
+   nothing, leaving the global objects to be cleaned up by the Operating System.
+
+   *Bob Beck*
+
  * Added CSHAKE as per [SP 800-185]

    *Shane Lontis*
diff --git a/crypto/dllmain.c b/crypto/dllmain.c
index 78eee3f4c7..6bc8edf0cc 100644
--- a/crypto/dllmain.c
+++ b/crypto/dllmain.c
@@ -37,6 +37,9 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
         OPENSSL_thread_stop();
         break;
     case DLL_PROCESS_DETACH:
+#if defined(OSSL_DLLMAIN_DESTRUCTOR)
+        ossl_cleanup_destructor();
+#endif /* defined(OSSL_DLLMAIN_DESTRUCTOR) */
         break;
     }
     return TRUE;
diff --git a/crypto/init.c b/crypto/init.c
index dd866d7e08..db9f18aee0 100644
--- a/crypto/init.c
+++ b/crypto/init.c
@@ -48,6 +48,7 @@ static CRYPTO_THREAD_LOCAL in_init_config_local;

 static CRYPTO_ONCE base = CRYPTO_ONCE_STATIC_INIT;
 static int base_inited = 0;
+static int do_global_cleanup = 0;
 DEFINE_RUN_ONCE_STATIC(ossl_init_base)
 {
     /* no need to init trace */
@@ -212,7 +213,28 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_async)
     return 1;
 }

-void OPENSSL_cleanup(void)
+/*
+ * Global cleanup function. This is optional, and not strictly
+ * necessary to run. Operating systems have successfully been
+ * recovering memory from exiting tasks since the days when I amused
+ * myself by drawing dinosaurs in crayon on used punch cards.
+ *
+ * If we have destructor support, this function is installed and
+ * always run as a global destructor. It only does anything if
+ * someone has called OPENSSL_cleanup() before it is run.
+ *
+ * This ensures that we do the actual cleanup requested by an
+ * OPENSSL_cleanup() only after subordinate library destructors which
+ * may call into OpenSSL have run.
+ *
+ * If we do not have destructor support, then this function is not
+ * normally run, and OPENSSL_cleanup() will do nothing. If we are
+ * compiled with the compile time define of
+ * DO_NOT_SKIP_OPENSSL_CLEANUP, this function will be called
+ * directly from OPENSSL_cleanup() so that cleanup will happen
+ * when OPENSSL_cleanup() is called.
+ */
+void ossl_cleanup_destructor(void)
 {
     /*
      * At some point we should consider looking at this function with a view to
@@ -223,6 +245,10 @@ void OPENSSL_cleanup(void)
     if (!base_inited)
         return;

+    /* If we have not been told to clean up, and we are invoked, return */
+    if (!do_global_cleanup)
+        return;
+
     /* Might be explicitly called */
     if (stopped)
         return;
@@ -463,6 +489,18 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
     return 1;
 }

+void OPENSSL_cleanup(void)
+{
+    do_global_cleanup = 1;
+#if defined(OSSL_CLEANUP_USING_DESTRUCTOR)
+    return;
+#endif /* defined(OSSL_CLEANUP_USING_DESTRUCTOR) */
+
+#if defined(DO_NOT_SKIP_OPENSSL_CLEANUP)
+    ossl_cleanup_destructor();
+#endif /* defined(DO_NOT_SKIP_OPENSSL_CLEANUP) */
+}
+
 int OPENSSL_atexit(void (*handler)(void))
 {
 #if defined(__TANDEM)
diff --git a/doc/man3/OPENSSL_init_crypto.pod b/doc/man3/OPENSSL_init_crypto.pod
index 830ace5f7c..f79d1b97dc 100644
--- a/doc/man3/OPENSSL_init_crypto.pod
+++ b/doc/man3/OPENSSL_init_crypto.pod
@@ -135,22 +135,28 @@ OPENSSL_init_crypto(). For example:
  OPENSSL_init_crypto(OPENSSL_INIT_NO_ADD_ALL_CIPHERS
                      | OPENSSL_INIT_NO_ADD_ALL_DIGESTS, NULL);

-The OPENSSL_cleanup() function deinitialises OpenSSL (both libcrypto
-and libssl). All resources allocated by OpenSSL are freed.  An application
-using the OpenSSL library may call this function to free library resources
-prior to application exit.  Note that there are some
-use cases in which subordinate libraries may also use OpenSSL and may not
-be finished with their references to it at application exit time (for example,
-if a subordinate library attempts to free an OpenSSL resource from a library
-destructor, calling OPENSSL_cleanup() may result in crashes or other unexpected
-behavior). If this is likely to be a problem then it is recommended that OPENSSL_cleanup()
-not be called, allowing the operating system to reap all library resources on process
-exit.
-
-Note, this may, on some leak detection tools (like valgrind) result in
-reports that indicate reachable memory remains on exit in certain
-configurations.  As these are not formally leaks, it is recommended that
-reachable memory reports be suppressed when running such tools.
+The OPENSSL_cleanup() function requests deinitialization of OpenSSL
+(both libcrypto and libssl). OpenSSL installs a global destructor
+function at initialization time if the toolchain supports this. This
+destructor is run after exit and after subordinate library destructors
+have run. By default the destructor does nothing. If deinitialization
+has been requested by a call to OPENSSL_cleanup(), then the destructor
+frees all global resources allocated by OpenSSL.
+
+If the toolchain building OpenSSL does not support a global
+destructor, by default OPENSSL_cleanup will do nothing, as this is the
+safest option, allowing the operating system to then reap all library
+resources on process exit. Note, this may, on some leak detection
+tools (like valgrind) result in reports that indicate reachable memory
+remains on exit in certain configurations.  As these are not formally
+leaks, it is recommended that reachable memory reports be suppressed
+when running such tools.
+
+If OpenSSL is compiled with the compile time option of DO_NOT_SKIP_OPENSSL_CLEANUP
+OPENSSL_cleanup() will deinitialize the library immediately when called. This
+is not normally recommended unless you are certain that the global resources
+will not be used by something after the point at which OPENSSL_cleanup() is
+called.

 Once OPENSSL_cleanup() has been called the library cannot be reinitialised.
 Attempts to call OPENSSL_init_crypto() will fail and an ERR_R_INIT_FAIL error
diff --git a/include/internal/e_os.h b/include/internal/e_os.h
index a237005e7e..1cec9d7875 100644
--- a/include/internal/e_os.h
+++ b/include/internal/e_os.h
@@ -366,3 +366,48 @@ typedef _locale_t locale_t;
 #endif

 #endif
+
+/*
+ * Can we use a global destructor?  We can use a global destructor via
+ * __attribute__ on anything like a modern gcc/clang.  We can also use
+ * it via dllmain on anything win32/win64.
+ *
+ * Older things may not do this.
+ * The assumption here is then if you don't have destructor support,
+ * it is safe to call OPENSSL_cleanup before an application exits
+ * because no library it is linked with will run code in a destructor
+ * that will call into OpenSSL after exit() happens.
+ *
+ */
+#if defined(OPENSSL_SYS_WIN32) || defined(OPENSSL_SYS_WIN64)
+#define OSSL_CLEANUP_USING_DESTRUCTOR
+#define OSSL_DLLMAIN_DESTRUCTOR
+/*
+ * destructor will be installed in libcrypto's dllmain.c
+ * This means effectively anything not win16 or dos will handle
+ * this.
+ */
+void ossl_cleanup_destructor(void);
+#else
+#if defined(__has_attribute)
+#if __has_attribute(destructor)
+/*
+ * This seems to have been a thing with any gcc or clang since the
+ * early 2000's. So this could pretty much instead be just unconditional
+ * on __GNUC__ or __clang__.
+ */
+#define OSSL_CLEANUP_USING_DESTRUCTOR
+/* destructor is installed by compiler */
+void ossl_cleanup_destructor(void) __attribute__((destructor));
+#else
+/* We are not using a destructor */
+/*
+ * So we are on something that is not close to Windows or being
+ * compiled with a modern GCC/Clang derivative. either way
+ * this probably means something like a toolchain that is
+ * more than 20 years old.
+ */
+void ossl_cleanup_destructor(void);
+#endif /* defined (__has_attribute(destructor) */
+#endif /* defined (__has_attribute) */
+#endif /* defined(OPENSSL_SYS_WIN32) || defined(OPENSSL_SYS_WIN64) */