// Copyright 2017 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "absl/random/internal/seed_material.h" #include #ifndef _WIN32 #include #else #include #endif #include #include #include #include #include #include "absl/base/dynamic_annotations.h" #include "absl/base/internal/raw_logging.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" #if defined(__native_client__) #include #define ABSL_RANDOM_USE_NACL_SECURE_RANDOM 1 #elif defined(_WIN32) #include #define ABSL_RANDOM_USE_BCRYPT 1 #pragma comment(lib, "bcrypt.lib") #elif defined(__Fuchsia__) #include #endif #if defined(__GLIBC__) && \ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)) // glibc >= 2.25 has getentropy() #define ABSL_RANDOM_USE_GET_ENTROPY 1 #endif #if defined(ABSL_RANDOM_USE_BCRYPT) #include #ifndef BCRYPT_SUCCESS #define BCRYPT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) #endif // Also link bcrypt; this can be done via linker options or: // #pragma comment(lib, "bcrypt.lib") #endif namespace absl { ABSL_NAMESPACE_BEGIN namespace random_internal { namespace { // Read OS Entropy for random number seeds. // TODO(absl-team): Possibly place a cap on how much entropy may be read at a // time. #if defined(ABSL_RANDOM_USE_BCRYPT) // On Windows potentially use the BCRYPT CNG API to read available entropy. bool ReadSeedMaterialFromOSEntropyImpl(absl::Span values) { BCRYPT_ALG_HANDLE hProvider; NTSTATUS ret; ret = BCryptOpenAlgorithmProvider(&hProvider, BCRYPT_RNG_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0); if (!(BCRYPT_SUCCESS(ret))) { ABSL_RAW_LOG(ERROR, "Failed to open crypto provider."); return false; } ret = BCryptGenRandom( hProvider, // provider reinterpret_cast(values.data()), // buffer static_cast(sizeof(uint32_t) * values.size()), // bytes 0); // flags BCryptCloseAlgorithmProvider(hProvider, 0); return BCRYPT_SUCCESS(ret); } #elif defined(ABSL_RANDOM_USE_NACL_SECURE_RANDOM) // On NaCL use nacl_secure_random to acquire bytes. bool ReadSeedMaterialFromOSEntropyImpl(absl::Span values) { auto buffer = reinterpret_cast(values.data()); size_t buffer_size = sizeof(uint32_t) * values.size(); uint8_t* output_ptr = buffer; while (buffer_size > 0) { size_t nread = 0; const int error = nacl_secure_random(output_ptr, buffer_size, &nread); if (error != 0 || nread > buffer_size) { ABSL_RAW_LOG(ERROR, "Failed to read secure_random seed data: %d", error); return false; } output_ptr += nread; buffer_size -= nread; } return true; } #elif defined(__Fuchsia__) bool ReadSeedMaterialFromOSEntropyImpl(absl::Span values) { auto buffer = reinterpret_cast(values.data()); size_t buffer_size = sizeof(uint32_t) * values.size(); zx_cprng_draw(buffer, buffer_size); return true; } #else #if defined(ABSL_RANDOM_USE_GET_ENTROPY) // On *nix, use getentropy() if supported. Note that libc may support // getentropy(), but the kernel may not, in which case this function will return // false. bool ReadSeedMaterialFromGetEntropy(absl::Span values) { auto buffer = reinterpret_cast(values.data()); size_t buffer_size = sizeof(uint32_t) * values.size(); while (buffer_size > 0) { // getentropy() has a maximum permitted length of 256. size_t to_read = std::min(buffer_size, 256); int result = getentropy(buffer, to_read); if (result < 0) { return false; } // https://github.com/google/sanitizers/issues/1173 // MemorySanitizer can't see through getentropy(). ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(buffer, to_read); buffer += to_read; buffer_size -= to_read; } return true; } #endif // defined(ABSL_RANDOM_GETENTROPY) // On *nix, read entropy from /dev/urandom. bool ReadSeedMaterialFromDevURandom(absl::Span values) { const char kEntropyFile[] = "/dev/urandom"; auto buffer = reinterpret_cast(values.data()); size_t buffer_size = sizeof(uint32_t) * values.size(); int dev_urandom = open(kEntropyFile, O_RDONLY); bool success = (-1 != dev_urandom); if (!success) { return false; } while (success && buffer_size > 0) { int bytes_read = read(dev_urandom, buffer, buffer_size); int read_error = errno; success = (bytes_read > 0); if (success) { buffer += bytes_read; buffer_size -= bytes_read; } else if (bytes_read == -1 && read_error == EINTR) { success = true; // Need to try again. } } close(dev_urandom); return success; } bool ReadSeedMaterialFromOSEntropyImpl(absl::Span values) { #if defined(ABSL_RANDOM_USE_GET_ENTROPY) if (ReadSeedMaterialFromGetEntropy(values)) { return true; } #endif // Libc may support getentropy, but the kernel may not, so we still have // to fallback to ReadSeedMaterialFromDevURandom(). return ReadSeedMaterialFromDevURandom(values); } #endif } // namespace bool ReadSeedMaterialFromOSEntropy(absl::Span values) { assert(values.data() != nullptr); if (values.data() == nullptr) { return false; } if (values.empty()) { return true; } return ReadSeedMaterialFromOSEntropyImpl(values); } void MixIntoSeedMaterial(absl::Span sequence, absl::Span seed_material) { // Algorithm is based on code available at // https://gist.github.com/imneme/540829265469e673d045 constexpr uint32_t kInitVal = 0x43b0d7e5; constexpr uint32_t kHashMul = 0x931e8875; constexpr uint32_t kMixMulL = 0xca01f9dd; constexpr uint32_t kMixMulR = 0x4973f715; constexpr uint32_t kShiftSize = sizeof(uint32_t) * 8 / 2; uint32_t hash_const = kInitVal; auto hash = [&](uint32_t value) { value ^= hash_const; hash_const *= kHashMul; value *= hash_const; value ^= value >> kShiftSize; return value; }; auto mix = [&](uint32_t x, uint32_t y) { uint32_t result = kMixMulL * x - kMixMulR * y; result ^= result >> kShiftSize; return result; }; for (const auto& seq_val : sequence) { for (auto& elem : seed_material) { elem = mix(elem, hash(seq_val)); } } } absl::optional GetSaltMaterial() { // Salt must be common for all generators within the same process so read it // only once and store in static variable. static const auto salt_material = []() -> absl::optional { uint32_t salt_value = 0; if (random_internal::ReadSeedMaterialFromOSEntropy( MakeSpan(&salt_value, 1))) { return salt_value; } return absl::nullopt; }(); return salt_material; } } // namespace random_internal ABSL_NAMESPACE_END } // namespace absl