Avoid errors when loading a cert multiple times.
[openssl.git] / crypto / init.c
index ace11da610e570b543a2c94a44b47676e77a380f..00a91792d8387149f905f1cd147b138ed0df0b12 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the OpenSSL license (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
 #include <stdlib.h>
 #include <assert.h>
 #include <internal/thread_once.h>
+#include <internal/dso.h>
 
 static int stopped = 0;
 
-static void ossl_init_thread_stop(struct thread_local_inits_st *locals);
+/*
+ * Since per-thread-specific-data destructors are not universally
+ * available, i.e. not on Windows, only below CRYPTO_THREAD_LOCAL key
+ * is assumed to have destructor associated. And then an effort is made
+ * to call this single destructor on non-pthread platform[s].
+ *
+ * Initial value is "impossible". It is used as guard value to shortcut
+ * destructor for threads terminating before libcrypto is initialized or
+ * after it's de-initialized. Access to the key doesn't have to be
+ * serialized for the said threads, because they didn't use libcrypto
+ * and it doesn't matter if they pick "impossible" or derefernce real
+ * key value and pull NULL past initialization in the first thread that
+ * intends to use libcrypto.
+ */
+static CRYPTO_THREAD_LOCAL destructor_key = (CRYPTO_THREAD_LOCAL)-1;
 
-static CRYPTO_THREAD_LOCAL threadstopkey;
+static void ossl_init_thread_stop(struct thread_local_inits_st *locals);
 
-static void ossl_init_thread_stop_wrap(void *local)
+static void ossl_init_thread_destructor(void *local)
 {
     ossl_init_thread_stop((struct thread_local_inits_st *)local);
 }
@@ -38,14 +53,17 @@ static void ossl_init_thread_stop_wrap(void *local)
 static struct thread_local_inits_st *ossl_init_get_thread_local(int alloc)
 {
     struct thread_local_inits_st *local =
-        CRYPTO_THREAD_get_local(&threadstopkey);
-
-    if (local == NULL && alloc) {
-        local = OPENSSL_zalloc(sizeof *local);
-        CRYPTO_THREAD_set_local(&threadstopkey, local);
-    }
-    if (!alloc) {
-        CRYPTO_THREAD_set_local(&threadstopkey, NULL);
+        CRYPTO_THREAD_get_local(&destructor_key);
+
+    if (alloc) {
+        if (local == NULL
+            && (local = OPENSSL_zalloc(sizeof(*local))) != NULL
+            && !CRYPTO_THREAD_set_local(&destructor_key, local)) {
+            OPENSSL_free(local);
+            return NULL;
+        }
+    } else {
+        CRYPTO_THREAD_set_local(&destructor_key, NULL);
     }
 
     return local;
@@ -64,22 +82,88 @@ static CRYPTO_ONCE base = CRYPTO_ONCE_STATIC_INIT;
 static int base_inited = 0;
 DEFINE_RUN_ONCE_STATIC(ossl_init_base)
 {
+    CRYPTO_THREAD_LOCAL key;
+
 #ifdef OPENSSL_INIT_DEBUG
     fprintf(stderr, "OPENSSL_INIT: ossl_init_base: Setting up stop handlers\n");
 #endif
-    /*
-     * We use a dummy thread local key here. We use the destructor to detect
-     * when the thread is going to stop (where that feature is available)
-     */
-    CRYPTO_THREAD_init_local(&threadstopkey, ossl_init_thread_stop_wrap);
+    if (!CRYPTO_THREAD_init_local(&key, ossl_init_thread_destructor))
+        return 0;
+    if ((init_lock = CRYPTO_THREAD_lock_new()) == NULL)
+        goto err;
 #ifndef OPENSSL_SYS_UEFI
-    atexit(OPENSSL_cleanup);
+    if (atexit(OPENSSL_cleanup) != 0)
+        goto err;
 #endif
-    if ((init_lock = CRYPTO_THREAD_lock_new()) == NULL)
-        return 0;
     OPENSSL_cpuid_setup();
+
+    destructor_key = key;
     base_inited = 1;
     return 1;
+
+err:
+#ifdef OPENSSL_INIT_DEBUG
+    fprintf(stderr, "OPENSSL_INIT: ossl_init_base not ok!\n");
+#endif
+    CRYPTO_THREAD_lock_free(init_lock);
+    init_lock = NULL;
+
+    CRYPTO_THREAD_cleanup_local(&key);
+    return 0;
+}
+
+static CRYPTO_ONCE load_crypto_nodelete = CRYPTO_ONCE_STATIC_INIT;
+DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_nodelete)
+{
+#ifdef OPENSSL_INIT_DEBUG
+    fprintf(stderr, "OPENSSL_INIT: ossl_init_load_crypto_nodelete()\n");
+#endif
+#if !defined(OPENSSL_NO_DSO) && !defined(OPENSSL_USE_NODELETE)
+# ifdef DSO_WIN32
+    {
+        HMODULE handle = NULL;
+        BOOL ret;
+
+        /* 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,
+                                (void *)&base_inited, &handle);
+
+#  ifdef OPENSSL_INIT_DEBUG
+        fprintf(stderr, "OPENSSL_INIT: obtained DSO reference? %s\n",
+                (ret == TRUE ? "No!" : "Yes."));
+#  endif
+        return (ret == TRUE) ? 1 : 0;
+    }
+# else
+    /*
+     * Deliberately leak a reference to ourselves. This will force the library
+     * to remain loaded until the atexit() handler is run at process exit.
+     */
+    {
+        DSO *dso;
+        void *err;
+
+        if (!err_shelve_state(&err))
+            return 0;
+
+        dso = DSO_dsobyaddr(&base_inited, DSO_FLAG_NO_UNLOAD_ON_FREE);
+#  ifdef OPENSSL_INIT_DEBUG
+        fprintf(stderr, "OPENSSL_INIT: obtained DSO reference? %s\n",
+                (dso == NULL ? "No!" : "Yes."));
+        /*
+         * In case of No!, it is uncertain our exit()-handlers can still be
+         * called. After dlclose() the whole library might have been unloaded
+         * already.
+         */
+#  endif
+        DSO_free(dso);
+        err_unshelve_state(err);
+    }
+# endif
+#endif
+
+    return 1;
 }
 
 static CRYPTO_ONCE load_crypto_strings = CRYPTO_ONCE_STATIC_INIT;
@@ -92,6 +176,7 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_no_load_crypto_strings)
 
 DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_strings)
 {
+    int ret = 1;
     /*
      * OPENSSL_NO_AUTOERRINIT is provided here to prevent at compile time
      * pulling in all the error strings during static linking
@@ -101,10 +186,10 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_strings)
     fprintf(stderr, "OPENSSL_INIT: ossl_init_load_crypto_strings: "
                     "err_load_crypto_strings_int()\n");
 # endif
-    err_load_crypto_strings_int();
-#endif
+    ret = err_load_crypto_strings_int();
     load_crypto_strings_inited = 1;
-    return 1;
+#endif    
+    return ret;
 }
 
 static CRYPTO_ONCE add_all_ciphers = CRYPTO_ONCE_STATIC_INIT;
@@ -257,16 +342,6 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_engine_capi)
     return 1;
 }
 #  endif
-static CRYPTO_ONCE engine_dasync = CRYPTO_ONCE_STATIC_INIT;
-DEFINE_RUN_ONCE_STATIC(ossl_init_engine_dasync)
-{
-# ifdef OPENSSL_INIT_DEBUG
-    fprintf(stderr, "OPENSSL_INIT: ossl_init_engine_dasync: "
-                    "engine_load_dasync_int()\n");
-# endif
-    engine_load_dasync_int();
-    return 1;
-}
 #  if !defined(OPENSSL_NO_AFALGENG)
 static CRYPTO_ONCE engine_afalg = CRYPTO_ONCE_STATIC_INIT;
 DEFINE_RUN_ONCE_STATIC(ossl_init_engine_afalg)
@@ -303,9 +378,9 @@ static void ossl_init_thread_stop(struct thread_local_inits_st *locals)
     if (locals->async) {
 #ifdef OPENSSL_INIT_DEBUG
         fprintf(stderr, "OPENSSL_INIT: ossl_init_thread_stop: "
-                        "ASYNC_cleanup_thread()\n");
+                        "async_delete_thread_state()\n");
 #endif
-        ASYNC_cleanup_thread();
+        async_delete_thread_state();
     }
 
     if (locals->err_state) {
@@ -321,13 +396,18 @@ static void ossl_init_thread_stop(struct thread_local_inits_st *locals)
 
 void OPENSSL_thread_stop(void)
 {
-    ossl_init_thread_stop(
-        (struct thread_local_inits_st *)ossl_init_get_thread_local(0));
+    if (destructor_key != (CRYPTO_THREAD_LOCAL)-1)
+        ossl_init_thread_stop(ossl_init_get_thread_local(0));
 }
 
 int ossl_init_thread_start(uint64_t opts)
 {
-    struct thread_local_inits_st *locals = ossl_init_get_thread_local(1);
+    struct thread_local_inits_st *locals;
+
+    if (!OPENSSL_init_crypto(0, NULL))
+        return 0;
+
+    locals = ossl_init_get_thread_local(1);
 
     if (locals == NULL)
         return 0;
@@ -354,6 +434,7 @@ int ossl_init_thread_start(uint64_t opts)
 void OPENSSL_cleanup(void)
 {
     OPENSSL_INIT_STOP *currhandler, *lasthandler;
+    CRYPTO_THREAD_LOCAL key;
 
     /* If we've not been inited then no need to deinit */
     if (!base_inited)
@@ -412,7 +493,9 @@ void OPENSSL_cleanup(void)
         err_free_strings_int();
     }
 
-    CRYPTO_THREAD_cleanup_local(&threadstopkey);
+    key = destructor_key;
+    destructor_key = (CRYPTO_THREAD_LOCAL)-1;
+    CRYPTO_THREAD_cleanup_local(&key);
 
 #ifdef OPENSSL_INIT_DEBUG
     fprintf(stderr, "OPENSSL_INIT: OPENSSL_cleanup: "
@@ -468,24 +551,20 @@ void OPENSSL_cleanup(void)
  */
 int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
 {
-    static int stoperrset = 0;
-
     if (stopped) {
-        if (!stoperrset) {
-            /*
-             * We only ever set this once to avoid getting into an infinite
-             * loop where the error system keeps trying to init and fails so
-             * sets an error etc
-             */
-            stoperrset = 1;
+        if (!(opts & OPENSSL_INIT_BASE_ONLY))
             CRYPTOerr(CRYPTO_F_OPENSSL_INIT_CRYPTO, ERR_R_INIT_FAIL);
-        }
         return 0;
     }
 
     if (!RUN_ONCE(&base, ossl_init_base))
         return 0;
 
+    if (!(opts & OPENSSL_INIT_BASE_ONLY)
+            && !RUN_ONCE(&load_crypto_nodelete,
+                         ossl_init_load_crypto_nodelete))
+        return 0;
+
     if ((opts & OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS)
             && !RUN_ONCE(&load_crypto_strings,
                          ossl_init_no_load_crypto_strings))
@@ -558,9 +637,6 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
             && !RUN_ONCE(&engine_capi, ossl_init_engine_capi))
         return 0;
 #  endif
-    if ((opts & OPENSSL_INIT_ENGINE_DASYNC)
-            && !RUN_ONCE(&engine_dasync, ossl_init_engine_dasync))
-        return 0;
 #  if !defined(OPENSSL_NO_AFALGENG)
     if ((opts & OPENSSL_INIT_ENGINE_AFALG)
             && !RUN_ONCE(&engine_afalg, ossl_init_engine_afalg))
@@ -568,7 +644,7 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
 #  endif
 # endif
     if (opts & (OPENSSL_INIT_ENGINE_ALL_BUILTIN
-                | OPENSSL_INIT_ENGINE_DASYNC | OPENSSL_INIT_ENGINE_OPENSSL
+                | OPENSSL_INIT_ENGINE_OPENSSL
                 | OPENSSL_INIT_ENGINE_AFALG)) {
         ENGINE_register_all_complete();
     }
@@ -587,6 +663,55 @@ int OPENSSL_atexit(void (*handler)(void))
 {
     OPENSSL_INIT_STOP *newhand;
 
+#if !defined(OPENSSL_NO_DSO) && !defined(OPENSSL_USE_NODELETE)
+    {
+        union {
+            void *sym;
+            void (*func)(void);
+        } handlersym;
+
+        handlersym.func = handler;
+# ifdef DSO_WIN32
+        {
+            HMODULE handle = NULL;
+            BOOL ret;
+
+            /*
+             * 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;
+        }
+# else
+        /*
+         * 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;
+
+            ERR_set_mark();
+            dso = DSO_dsobyaddr(handlersym.sym, DSO_FLAG_NO_UNLOAD_ON_FREE);
+#  ifdef OPENSSL_INIT_DEBUG
+            fprintf(stderr,
+                    "OPENSSL_INIT: OPENSSL_atexit: obtained DSO reference? %s\n",
+                    (dso == NULL ? "No!" : "Yes."));
+            /* See same code above in ossl_init_base() for an explanation. */
+#  endif
+            DSO_free(dso);
+            ERR_pop_to_mark();
+        }
+# endif
+    }
+#endif
+
     newhand = OPENSSL_malloc(sizeof(*newhand));
     if (newhand == NULL)
         return 0;