Skip to content

Commit 864e97c

Browse files
committed
fix a bug where destroying a creds provider during a operation can lead to a use after free
1 parent b8aeefc commit 864e97c

2 files changed

Lines changed: 93 additions & 15 deletions

File tree

src/aws-cpp-sdk-core/source/auth/CrtCredentialsProvider.cpp

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,28 @@
44
*/
55
#include <aws/core/auth/CrtCredentialsProvider.h>
66
#include <aws/core/client/UserAgent.h>
7+
#include <aws/core/utils/memory/AWSMemory.h>
78
#include <aws/core/utils/threading/ReaderWriterLock.h>
89
#include <aws/crt/auth/Credentials.h>
910

11+
#include <condition_variable>
12+
#include <mutex>
13+
1014
using namespace Aws::Auth;
1115
using namespace Aws::Utils;
1216
using namespace Aws::Utils::Threading;
1317

1418
namespace {
1519
const int FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000;
16-
}
20+
const char* CRT_CREDS_PROVIDER_TAG = "CrtCredentialsProvider";
21+
22+
struct RefreshState {
23+
std::mutex mutex;
24+
std::condition_variable condition;
25+
bool complete{false};
26+
AWSCredentials credentials;
27+
};
28+
} // namespace
1729

1830
CrtCredentialsProvider::CrtCredentialsProvider(
1931
const std::function<std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>()>& credentialsProviderFactory,
@@ -39,27 +51,31 @@ AWSCredentials CrtCredentialsProvider::GetAWSCredentials() {
3951
}
4052

4153
void CrtCredentialsProvider::Reload() {
42-
AWSCredentials credentials{};
43-
std::mutex refresh_mutex{};
44-
std::condition_variable refresh_condition;
45-
bool refresh_complete{false};
46-
m_credentialsProvider->GetCredentials([&credentials, &refresh_mutex, &refresh_complete, &refresh_condition](
47-
const std::shared_ptr<Crt::Auth::Credentials>& crtCredentials, int errorCode) -> void {
54+
auto state = Aws::MakeShared<RefreshState>(CRT_CREDS_PROVIDER_TAG);
55+
56+
m_credentialsProvider->GetCredentials([state](const std::shared_ptr<Crt::Auth::Credentials>& crtCredentials, int errorCode) -> void {
57+
(void)errorCode;
4858
{
49-
const std::unique_lock<std::mutex> lock(refresh_mutex);
50-
(void)errorCode;
51-
credentials = ExtractCredentialsFromCrt(*crtCredentials);
52-
refresh_complete = true;
59+
const std::unique_lock<std::mutex> lock(state->mutex);
60+
if (crtCredentials) {
61+
state->credentials = ExtractCredentialsFromCrt(*crtCredentials);
62+
}
63+
state->complete = true;
64+
state->condition.notify_all();
5365
}
54-
refresh_condition.notify_all();
5566
});
5667

57-
std::unique_lock<std::mutex> lock(refresh_mutex);
58-
refresh_condition.wait_for(lock, m_providerFuturesTimeoutMs, [&refresh_complete]() -> bool { return refresh_complete; });
68+
AWSCredentials credentials{};
69+
{
70+
std::unique_lock<std::mutex> lock(state->mutex);
71+
state->condition.wait_for(lock, m_providerFuturesTimeoutMs, [&state]() -> bool { return state->complete; });
72+
credentials = state->credentials;
73+
}
74+
5975
if (!credentials.IsEmpty()) {
6076
credentials.AddUserAgentFeature(m_userAgentFeature);
6177
}
62-
m_credentials = credentials;
78+
m_credentials = std::move(credentials);
6379
}
6480

6581
void CrtCredentialsProvider::RefreshIfExpired() {

tests/aws-cpp-sdk-core-tests/aws/auth/CrtCredentialsProviderTest.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
#include <aws/crt/auth/Credentials.h>
88
#include <aws/testing/AwsCppSdkGTestSuite.h>
99

10+
#include <atomic>
11+
#include <chrono>
12+
#include <memory>
13+
#include <thread>
14+
1015
namespace {
1116
const char* CRT_CREDS_TEST_LOG = "CrtCredentialsProviderTest";
1217
}
@@ -56,8 +61,65 @@ class MockedCredsProvider : public Aws::Auth::CrtCredentialsProvider {
5661
std::shared_ptr<MockCrtCredentialsProvider> m_provider;
5762
};
5863

64+
class AsyncMockCrtCredentialsProvider : public Aws::Crt::Auth::ICredentialsProvider {
65+
public:
66+
explicit AsyncMockCrtCredentialsProvider(std::chrono::milliseconds callbackDelay,
67+
std::shared_ptr<Aws::Crt::Auth::Credentials> credentials)
68+
: m_callbackDelay{callbackDelay}, m_credentials{std::move(credentials)} {}
69+
70+
~AsyncMockCrtCredentialsProvider() override {
71+
if (m_worker.joinable()) {
72+
m_worker.join();
73+
}
74+
}
75+
76+
bool GetCredentials(const Aws::Crt::Auth::OnCredentialsResolved& onCredentialsResolved) const override {
77+
const std::chrono::milliseconds delay = m_callbackDelay;
78+
const std::shared_ptr<Aws::Crt::Auth::Credentials> creds = m_credentials;
79+
m_worker = std::thread([delay, creds, onCredentialsResolved]() {
80+
std::this_thread::sleep_for(delay);
81+
onCredentialsResolved(creds, AWS_OP_SUCCESS);
82+
});
83+
return true;
84+
}
85+
86+
aws_credentials_provider* GetUnderlyingHandle() const noexcept override { return nullptr; }
87+
bool IsValid() const noexcept override { return true; }
88+
89+
private:
90+
std::chrono::milliseconds m_callbackDelay;
91+
std::shared_ptr<Aws::Crt::Auth::Credentials> m_credentials;
92+
mutable std::thread m_worker;
93+
};
94+
95+
class AsyncMockedCredsProvider : public Aws::Auth::CrtCredentialsProvider {
96+
public:
97+
AsyncMockedCredsProvider(std::shared_ptr<AsyncMockCrtCredentialsProvider> provider, std::chrono::milliseconds timeout)
98+
: Aws::Auth::CrtCredentialsProvider([provider]() -> std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> { return provider; },
99+
timeout, Aws::Client::UserAgentFeature::CREDENTIALS_LOGIN, "AsyncMockedCredsProvider"),
100+
m_provider{provider} {}
101+
102+
private:
103+
std::shared_ptr<AsyncMockCrtCredentialsProvider> m_provider;
104+
};
105+
59106
class CrtCredentialsProviderTest : public Aws::Testing::AwsCppSdkGTestSuite {};
60107

108+
TEST_F(CrtCredentialsProviderTest, ShouldNotUseFreedStateWhenRefreshOutlivesTimeout) {
109+
auto crtCreds = Aws::MakeShared<Aws::Crt::Auth::Credentials>(
110+
CRT_CREDS_TEST_LOG, Aws::Crt::ByteCursorFromCString("access"), Aws::Crt::ByteCursorFromCString("secret"),
111+
Aws::Crt::ByteCursorFromCString("token"), static_cast<uint64_t>((Aws::Utils::DateTime::Now() + std::chrono::minutes(100)).Seconds()));
112+
113+
auto underlying_mock = Aws::MakeShared<AsyncMockCrtCredentialsProvider>(CRT_CREDS_TEST_LOG, std::chrono::milliseconds(300), crtCreds);
114+
115+
{
116+
AsyncMockedCredsProvider provider(underlying_mock, std::chrono::milliseconds(50));
117+
EXPECT_TRUE(provider.GetAWSCredentials().IsExpiredOrEmpty());
118+
}
119+
120+
std::this_thread::sleep_for(std::chrono::milliseconds(400));
121+
}
122+
61123
TEST_F(CrtCredentialsProviderTest, ShouldCache) {
62124
auto underlying_mock = Aws::MakeShared<MockCrtCredentialsProvider>(CRT_CREDS_TEST_LOG);
63125
underlying_mock->AddCredentialForReturn(Aws::MakeShared<Aws::Crt::Auth::Credentials>(

0 commit comments

Comments
 (0)