summaryrefslogtreecommitdiff
path: root/dev-qt/qtbase/files/qtbase-6.12.0-QTBUG-148575.patch
blob: 02ee2cea1a9de95e8962c0048e16467a98990662 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
https://qt-project.atlassian.net/browse/QTBUG-148575
https://codereview.qt-project.org/c/qt/qtbase/+/771398
--- a/src/corelib/tools/qcryptographichash.cpp
+++ b/src/corelib/tools/qcryptographichash.cpp
@@ -116,4 +116,5 @@
 #if !defined(QT_BOOTSTRAPPED) && QT_CONFIG(openssl_hash)
 #define USING_OPENSSL30
+#include <openssl/crypto.h>
 #include <openssl/evp.h>
 #include <openssl/provider.h>
@@ -213,4 +214,54 @@
     return nullptr;
 }
+
+/*
+    Never unloaded: it is the unload that trips OpenSSL up, freeing a stale
+    provider-store entry a second time at exit (QTBUG-148575;
+    openssl/openssl#32079 has the exact sequence, and is where to look before
+    removing any of this). retain_fallbacks=1 leaves OpenSSL's own fallback in
+    place for everyone else in the process, e.g. the TLS backend (QTBUG-136223).
+
+    Whether the load succeeded is of no interest to the callers: the
+    EVP_MD_fetch() that follows is the actual availability test, and it can
+    still succeed from the fallback or from another provider (e.g. FIPS).
+*/
+static void ensureDefaultProviderLoaded()
+{
+    static const bool loaded = OSSL_PROVIDER_try_load(nullptr, "default", /*retain_fallbacks=*/1);
+    Q_UNUSED(loaded);
+}
+
+namespace {
+struct OSSL_LIB_CTX_deleter {
+    void operator()(OSSL_LIB_CTX *ctx) const noexcept {
+        OSSL_LIB_CTX_free(ctx);
+    }
+};
+using OSSL_LIB_CTX_ptr = std::unique_ptr<OSSL_LIB_CTX, OSSL_LIB_CTX_deleter>;
+} // unnamed namespace
+
+/*
+    MD4 only exists in OpenSSL's "legacy" provider, which we don't want loaded
+    process-wide - other OpenSSL users in the process could then fetch other,
+    deliberately-weak algorithms from it too. Give it its own context instead:
+    load it (and its config, since OSSL_LIB_CTX_new() doesn't do that on its
+    own) once and keep it alive like ensureDefaultProviderLoaded() above.
+*/
+static OSSL_LIB_CTX *legacyProviderContext()
+{
+    static const OSSL_LIB_CTX_ptr ctx = [] {
+        OSSL_LIB_CTX_ptr ctx(OSSL_LIB_CTX_new());
+        if (ctx) {
+            // Unlike the global context's automatic init, this fails when
+            // there is no readable openssl.cnf.
+            const bool configLoaded = OSSL_LIB_CTX_load_config(ctx.get(), nullptr);
+            Q_UNUSED(configLoaded);
+            if (!OSSL_PROVIDER_load(ctx.get(), "legacy"))
+                ctx.reset();
+        }
+        return ctx;
+    }();
+    return ctx.get();
+}
 #endif // USING_OPENSSL30
 
@@ -251,18 +302,9 @@
         }
     };
-    struct OSSL_PROVIDER_deleter {
-        void operator()(OSSL_PROVIDER *provider) const noexcept {
-            OSSL_PROVIDER_unload(provider);
-        }
-    };
-
     using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, EVP_MD_CTX_deleter>;
     using EVP_MD_ptr = std::unique_ptr<EVP_MD, EVP_MD_deleter>;
-    using OSSL_PROVIDER_ptr = std::unique_ptr<OSSL_PROVIDER, OSSL_PROVIDER_deleter>;
     struct EVP {
         EVP_MD_ptr algorithm;
         EVP_MD_CTX_ptr context;
-        OSSL_PROVIDER_ptr defaultProvider;
-        OSSL_PROVIDER_ptr legacyProvider;
         bool initializationFailed;
 
@@ -546,27 +588,13 @@
     : initializationFailed{true}
 {
+    OSSL_LIB_CTX *fetchCtx = nullptr;
     if (method == QCryptographicHash::Md4) {
-        /*
-         * We need to load the legacy provider in order to have the MD4
-         * algorithm available.
-         */
-        legacyProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_try_load(nullptr, "legacy", /*retain_fallbacks=*/1));
-
-        if (!legacyProvider)
+        fetchCtx = legacyProviderContext();
+        if (!fetchCtx)
             return;
+    } else {
+        ensureDefaultProviderLoaded();
     }
 
-    /*
-     * Load with retain_fallbacks=1 so that loading these providers - and the
-     * unload performed by OSSL_PROVIDER_deleter - does not disable OpenSSL's
-     * fallback auto-loading of the default provider in the global library
-     * context. Plain OSSL_PROVIDER_load() disables that fallback, which then
-     * leaves later OpenSSL users (e.g. the TLS backend) without a default
-     * provider once we unload, breaking RAND seeding. See QTBUG-136223.
-     */
-    defaultProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_try_load(nullptr, "default", /*retain_fallbacks=*/1));
-    if (!defaultProvider)
-        return;
-
     context = EVP_MD_CTX_ptr(EVP_MD_CTX_new());
 
@@ -578,7 +606,8 @@
      * Using the "-fips" option will disable the global "fips=yes" for
      * this one lookup and the algorithm can be fetched from any provider
-     * that implements the algorithm (including the FIPS provider).
+     * that implements the algorithm (including the FIPS provider). For Md4
+     * that is moot: fetchCtx only ever holds the legacy provider.
      */
-    algorithm = EVP_MD_ptr(EVP_MD_fetch(nullptr, methodToName(method), "-fips"));
+    algorithm = EVP_MD_ptr{EVP_MD_fetch(fetchCtx, methodToName(method), "-fips")};
     if (!algorithm) {
         return;
@@ -1241,6 +1270,11 @@
     case QCryptographicHash::Blake2s_224:
         return true;
-    case QCryptographicHash::Sha1:
     case QCryptographicHash::Md4:
+        if (OSSL_LIB_CTX *legacyLibCtx = legacyProviderContext()) {
+            return EVP_MD_ptr{EVP_MD_fetch(legacyLibCtx, methodToName(method), "-fips")}
+                != nullptr;
+        }
+        return false;
+    case QCryptographicHash::Sha1:
     case QCryptographicHash::Md5:
     case QCryptographicHash::Sha224:
@@ -1254,8 +1288,5 @@
     case QCryptographicHash::Blake2b_512:
     case QCryptographicHash::Blake2s_256: {
-    // retain_fallbacks=1: don't disable the global default-provider fallback
-    // auto-load (see QTBUG-136223 and the EVP constructor above).
-    auto legacyProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_try_load(nullptr, "legacy", /*retain_fallbacks=*/1));
-    auto defaultProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_try_load(nullptr, "default", /*retain_fallbacks=*/1));
+    ensureDefaultProviderLoaded();
 
     const char *restriction = "-fips";
--- a/tests/auto/network/ssl/qsslsocket/stresstest_hash_before_ssl/opensslhash_before_ssl.cpp
+++ b/tests/auto/network/ssl/qsslsocket/stresstest_hash_before_ssl/opensslhash_before_ssl.cpp
@@ -20,7 +20,9 @@
 
     // Use QCryptographicHash before any SSL API is touched.  When
-    // openssl_hash is enabled, the destructor calls OSSL_PROVIDER_unload()
-    // on the default provider, draining its activation count to zero.
-    // ensureLibraryLoaded() (called below via supportsSsl()) must recover.
+    // openssl_hash is enabled, this loads the default provider into the
+    // global library context and, as of QTBUG-148575, leaves it loaded;
+    // ensureLibraryLoaded() (called below via supportsSsl()) must come up
+    // regardless.  It used to unload the provider again, draining its
+    // activation count to zero and leaving SSL without one (QTBUG-136223).
     {
         QCryptographicHash hash(QCryptographicHash::Sha256);