Merge pull request #14222 from lovesegfault/curl-based-s3-fix-race

fix(libstore): fix race condition in AWS credential provider caching
This commit is contained in:
John Ericson
2025-10-12 22:07:10 +00:00
committed by GitHub

View File

@@ -118,11 +118,14 @@ AwsCredentials getAwsCredentials(const std::string & profile)
// Get or create credential provider with caching // Get or create credential provider with caching
std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider; std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider;
// Try to find existing provider // Use try_emplace_and_cvisit for atomic get-or-create
credentialProviderCache.visit(profile, [&](const auto & pair) { provider = pair.second; }); // This prevents race conditions where multiple threads create providers
credentialProviderCache.try_emplace_and_cvisit(
if (!provider) { profile,
// Create new provider if not found nullptr, // Placeholder - will be replaced in f1 before any thread can see it
[&](auto & kv) {
// f1: Called atomically during insertion with non-const reference
// Other threads are blocked until we finish, so nullptr is never visible
debug( debug(
"[pid=%d] creating new AWS credential provider for profile '%s'", "[pid=%d] creating new AWS credential provider for profile '%s'",
getpid(), getpid(),
@@ -134,32 +137,38 @@ AwsCredentials getAwsCredentials(const std::string & profile)
if (profile.empty()) { if (profile.empty()) {
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config; Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap(); config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
provider = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config); kv.second = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config);
} else { } else {
Aws::Crt::Auth::CredentialsProviderProfileConfig config; Aws::Crt::Auth::CredentialsProviderProfileConfig config;
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap(); config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
// This is safe because the underlying C library will copy this string // This is safe because the underlying C library will copy this string
// c.f. https://github.com/awslabs/aws-c-auth/blob/main/source/credentials_provider_profile.c#L220 // c.f. https://github.com/awslabs/aws-c-auth/blob/main/source/credentials_provider_profile.c#L220
config.ProfileNameOverride = Aws::Crt::ByteCursorFromCString(profile.c_str()); config.ProfileNameOverride = Aws::Crt::ByteCursorFromCString(profile.c_str());
provider = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config); kv.second = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config);
}
} catch (Error & e) {
e.addTrace(
{},
"while creating AWS credentials provider for %s",
profile.empty() ? "default profile" : fmt("profile '%s'", profile));
throw;
} }
if (!provider) { if (!kv.second) {
throw AwsAuthError( throw AwsAuthError(
"Failed to create AWS credentials provider for %s", "Failed to create AWS credentials provider for %s",
profile.empty() ? "default profile" : fmt("profile '%s'", profile)); profile.empty() ? "default profile" : fmt("profile '%s'", profile));
} }
// Insert into cache (try_emplace is thread-safe and won't overwrite if another thread added it) provider = kv.second;
credentialProviderCache.try_emplace(profile, provider); } catch (Error & e) {
// Exception during creation - remove the entry to allow retry
credentialProviderCache.erase(profile);
e.addTrace({}, "for AWS profile: %s", profile.empty() ? "(default)" : profile);
throw;
} catch (...) {
// Non-Error exception - still need to clean up
credentialProviderCache.erase(profile);
throw;
} }
},
[&](const auto & kv) {
// f2: Called if key already exists (const reference)
provider = kv.second;
});
return getCredentialsFromProvider(provider); return getCredentialsFromProvider(provider);
} }