1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 19:46:02 +01:00

libstore: Fix reentrancy in AwsCredentialProviderImpl::getCredentialsRaw

Old code would do very much incorrect reentrancy crimes (trying to do an
erase inside the emplace callback). This would fail miserably with an assertion
in Boost:

terminating due to unexpected unrecoverable internal error: Assertion '(!find(px))&&("reentrancy not allowed")' failed in boost::unordered::detail::foa::entry_trace::entry_trace(const void *) at include/boost/unordered/detail/foa/reentrancy_check.hpp:33

This is trivially reproduced by using any S3 URL with a non-empty profile:

nix-prefetch-url "s3://happy/crash?profile=default"
This commit is contained in:
Sergei Zimmerman 2025-10-19 21:03:13 +03:00
parent 3c03050cd6
commit c663f7ec79
No known key found for this signature in database

View file

@ -96,67 +96,57 @@ public:
} }
} }
std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> createProviderForProfile(const std::string & profile);
private: private:
Aws::Crt::ApiHandle apiHandle; Aws::Crt::ApiHandle apiHandle;
boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>> boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>>
credentialProviderCache; credentialProviderCache;
}; };
AwsCredentials AwsCredentialProviderImpl::getCredentialsRaw(const std::string & profile) std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>
AwsCredentialProviderImpl::createProviderForProfile(const std::string & profile)
{ {
// Get or create credential provider with caching
std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider;
// Use try_emplace_and_cvisit for atomic get-or-create
// This prevents race conditions where multiple threads create providers
credentialProviderCache.try_emplace_and_cvisit(
profile,
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(),
profile.empty() ? "(default)" : profile.c_str()); profile.empty() ? "(default)" : profile.c_str());
try {
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();
kv.second = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config); return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config);
} 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());
kv.second = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config); return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config);
} }
AwsCredentials AwsCredentialProviderImpl::getCredentialsRaw(const std::string & profile)
{
std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider;
credentialProviderCache.try_emplace_and_cvisit(
profile,
nullptr,
[&](auto & kv) { provider = kv.second = createProviderForProfile(profile); },
[&](const auto & kv) { provider = kv.second; });
if (!provider) {
credentialProviderCache.erase_if(profile, [](const auto & kv) {
[[maybe_unused]] auto [_, provider] = kv;
return !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));
} }
provider = kv.second;
} 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);
} }