Skip to content

Commit 263a148

Browse files
bneradtywkaras
authored andcommitted
YTSATS-4067: Fix deadlock with secret_map_mutex (#740)
1. getOrLoadSecret grabbed the secret_map_mutex and called loadSecret. 2. loadSecret dispatched to Continations that registered for the TS_EVENT_SSL_SECRET event. This would try to grab the Continuation's lock. 3. In the meantime, those Continuations could call setSecret which would try to grab the secret_map_mutex. If this Continuation did this while holding the lock that step 2 is waiting upon, then there will be a deadlock between the Continuation lock and the secret_map_mutex between the two threads. This patch avoids the deadlock by releasing the secret_map_mutex lock before calling loadSecret. It also updates the secret_map when loading secrets from a file in loadSecret.
1 parent 3222476 commit 263a148

File tree

1 file changed

+29
-14
lines changed

1 file changed

+29
-14
lines changed

iocore/net/SSLSecret.cc

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@
2323
#include "tscore/ts_file.h"
2424
#include "P_SSLConfig.h"
2525

26+
#include <utility>
27+
28+
// NOTE: The secret_map_mutex should not be held by the caller of this
29+
// function. The implementation of this function may call a plugin's
30+
// TS_EVENT_SSL_SECRET handler which in turn may grab a lock for
31+
// secret_map_mutex via a TSSslSecretSet call. These events will result in a
32+
// deadlock.
2633
void
2734
SSLSecret::loadSecret(const std::string &name1, const std::string &name2, std::string &data1, std::string &data2)
2835
{
@@ -44,8 +51,10 @@ SSLSecret::loadSecret(const std::string &name1, const std::string &name2, std::s
4451
if (data1.empty() || (!name2.empty() && data2.empty())) {
4552
// If none of them loaded it, assume it is a file
4653
data1 = loadFile(name1);
54+
setSecret(name1, data1);
4755
if (!name2.empty()) {
4856
data2 = loadFile(name2);
57+
setSecret(name2, data2);
4958
}
5059
}
5160
}
@@ -100,20 +109,26 @@ SSLSecret::getSecret(const std::string &name) const
100109
void
101110
SSLSecret::getOrLoadSecret(const std::string &name1, const std::string &name2, std::string &data1, std::string &data2)
102111
{
103-
Debug("ssl_secret", "lookup up secrets for %s and %s", name1.c_str(), name2.empty() ? "[empty]" : name2.c_str());
104-
std::scoped_lock lock(secret_map_mutex);
105-
std::string *const data1ptr = &(secret_map[name1]);
106-
std::string *const data2ptr = [&]() -> std::string *const {
107-
if (name2.empty()) {
108-
data2.clear();
109-
return &data2;
110-
}
111-
return &(secret_map[name2]);
112-
}();
112+
Debug("ssl_secret", "lookup up secrets for %s and %s", name1.c_str(), name2.c_str());
113+
{
114+
std::scoped_lock lock(secret_map_mutex);
115+
std::string *const data1ptr = &(secret_map[name1]);
116+
std::string *const data2ptr = [&]() -> std::string *const {
117+
if (name2.empty()) {
118+
data2.clear();
119+
return &data2;
120+
}
121+
return &(secret_map[name2]);
122+
}();
123+
data1 = *data1ptr;
124+
data2 = *data2ptr;
125+
}
113126
// If we can't find either secret, load them both again
114-
if (data1ptr->empty() || (!name2.empty() && data2ptr->empty())) {
115-
this->loadSecret(name1, name2, *data1ptr, *data2ptr);
127+
if (data1.empty() || (!name2.empty() && data2.empty())) {
128+
std::string data1tmp;
129+
std::string data2tmp;
130+
this->loadSecret(name1, name2, data1tmp, data2tmp);
131+
data1 = std::move(data1tmp);
132+
data2 = std::move(data2tmp);
116133
}
117-
data1 = *data1ptr;
118-
data2 = *data2ptr;
119134
}

0 commit comments

Comments
 (0)