1/*
2 * Copyright 2017-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <folly/ssl/detail/OpenSSLThreading.h>
18
19#include <memory>
20#include <mutex>
21
22#include <folly/Portability.h>
23#include <folly/SharedMutex.h>
24#include <folly/SpinLock.h>
25
26#include <glog/logging.h>
27
28// We cannot directly use portability/openssl because it also depends on us.
29// Therefore we directly use openssl includes. Order of includes is important
30// here. See portability/openssl.h.
31#include <folly/portability/Windows.h>
32#include <openssl/crypto.h>
33
34#if !defined(OPENSSL_IS_BORINGSSL)
35#define FOLLY_SSL_DETAIL_OPENSSL_IS_110 (OPENSSL_VERSION_NUMBER >= 0x10100000L)
36#else
37#define FOLLY_SSL_DETAIL_OPENSSL_IS_110 (false)
38#endif
39
40// OpenSSL requires us to provide the implementation of CRYPTO_dynlock_value
41// so it must be done in the global namespace.
42struct CRYPTO_dynlock_value {
43 std::mutex mutex;
44};
45
46namespace folly {
47namespace ssl {
48namespace detail {
49
50static std::map<int, LockType>& lockTypes() {
51 static auto lockTypesInst = new std::map<int, LockType>();
52 return *lockTypesInst;
53}
54
55void setLockTypes(std::map<int, LockType> inLockTypes) {
56#if FOLLY_SSL_DETAIL_OPENSSL_IS_110
57 LOG(INFO) << "setLockTypes() is unsupported on OpenSSL >= 1.1.0. "
58 << "OpenSSL now uses platform native mutexes";
59#endif
60
61 lockTypes() = inLockTypes;
62}
63
64bool isSSLLockDisabled(int lockId) {
65 const auto& sslLocks = lockTypes();
66 const auto it = sslLocks.find(lockId);
67 return it != sslLocks.end() && it->second == LockType::NONE;
68}
69
70namespace {
71struct SSLLock {
72 explicit SSLLock(LockType inLockType = LockType::MUTEX)
73 : lockType(inLockType) {}
74
75 void lock(bool read) {
76 if (lockType == LockType::MUTEX) {
77 mutex.lock();
78 } else if (lockType == LockType::SPINLOCK) {
79 spinLock.lock();
80 } else if (lockType == LockType::SHAREDMUTEX) {
81 if (read) {
82 sharedMutex.lock_shared();
83 } else {
84 sharedMutex.lock();
85 }
86 }
87 // lockType == LOCK_NONE, no-op
88 }
89
90 void unlock(bool read) {
91 if (lockType == LockType::MUTEX) {
92 mutex.unlock();
93 } else if (lockType == LockType::SPINLOCK) {
94 spinLock.unlock();
95 } else if (lockType == LockType::SHAREDMUTEX) {
96 if (read) {
97 sharedMutex.unlock_shared();
98 } else {
99 sharedMutex.unlock();
100 }
101 }
102 // lockType == LOCK_NONE, no-op
103 }
104
105 LockType lockType;
106 folly::SpinLock spinLock{};
107 std::mutex mutex;
108 SharedMutex sharedMutex;
109};
110} // namespace
111
112// Statics are unsafe in environments that call exit().
113// If one thread calls exit() while another thread is
114// references a member of SSLContext, bad things can happen.
115// SSLContext runs in such environments.
116// Instead of declaring a static member we "new" the static
117// member so that it won't be destructed on exit().
118static std::unique_ptr<SSLLock[]>& locks() {
119 static auto locksInst = new std::unique_ptr<SSLLock[]>();
120 return *locksInst;
121}
122
123static void callbackLocking(int mode, int n, const char*, int) {
124 if (mode & CRYPTO_LOCK) {
125 locks()[size_t(n)].lock(mode & CRYPTO_READ);
126 } else {
127 locks()[size_t(n)].unlock(mode & CRYPTO_READ);
128 }
129}
130
131static unsigned long callbackThreadID() {
132 return static_cast<unsigned long>(folly::getCurrentThreadID());
133}
134
135static CRYPTO_dynlock_value* dyn_create(const char*, int) {
136 return new CRYPTO_dynlock_value;
137}
138
139static void
140dyn_lock(int mode, struct CRYPTO_dynlock_value* lock, const char*, int) {
141 if (lock != nullptr) {
142 if (mode & CRYPTO_LOCK) {
143 lock->mutex.lock();
144 } else {
145 lock->mutex.unlock();
146 }
147 }
148}
149
150static void dyn_destroy(struct CRYPTO_dynlock_value* lock, const char*, int) {
151 delete lock;
152}
153
154void installThreadingLocks() {
155 // static locking
156 locks() = std::make_unique<SSLLock[]>(size_t(CRYPTO_num_locks()));
157 for (auto it : lockTypes()) {
158 locks()[size_t(it.first)].lockType = it.second;
159 }
160 CRYPTO_set_id_callback(callbackThreadID);
161 CRYPTO_set_locking_callback(callbackLocking);
162 // dynamic locking
163 CRYPTO_set_dynlock_create_callback(dyn_create);
164 CRYPTO_set_dynlock_lock_callback(dyn_lock);
165 CRYPTO_set_dynlock_destroy_callback(dyn_destroy);
166}
167
168void cleanupThreadingLocks() {
169 CRYPTO_set_id_callback(nullptr);
170 CRYPTO_set_locking_callback(nullptr);
171 CRYPTO_set_dynlock_create_callback(nullptr);
172 CRYPTO_set_dynlock_lock_callback(nullptr);
173 CRYPTO_set_dynlock_destroy_callback(nullptr);
174 locks().reset();
175}
176
177} // namespace detail
178} // namespace ssl
179} // namespace folly
180