blob: fa2d4ef92ab14732c826e926dc51491b2df2c544 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <assert.h>
#include <ctype.h>
#include <lib/boot-options/boot-options.h>
#include <lib/crypto/entropy/collector.h>
#include <lib/crypto/entropy/hw_rng_collector.h>
#include <lib/crypto/entropy/jitterentropy_collector.h>
#include <lib/crypto/entropy/quality_test.h>
#include <lib/crypto/global_prng.h>
#include <lib/crypto/prng.h>
#include <lib/zbitl/view.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <new>
#include <explicit-memory/bytes.h>
#include <fbl/algorithm.h>
#include <kernel/auto_lock.h>
#include <kernel/mutex.h>
#include <kernel/thread.h>
#include <ktl/algorithm.h>
#include <ktl/byte.h>
#include <ktl/span.h>
#include <ktl/string_view.h>
#include <lk/init.h>
// See note in //zircon/third_party/ulib/boringssl/BUILD.gn
#define BORINGSSL_NO_CXX
#include <openssl/sha.h>
#define LOCAL_TRACE 0
namespace crypto {
namespace GlobalPRNG {
static PRNG* kGlobalPrng = nullptr;
PRNG* GetInstance() {
ASSERT(kGlobalPrng);
return kGlobalPrng;
}
static unsigned int IntegrateZbiEntropy() {
auto zbi_bytes = zbitl::StorageFromRawHeader(platform_get_zbi());
zbitl::View<ktl::span<ktl::byte>> zbi(
{const_cast<ktl::byte*>(zbi_bytes.data()), zbi_bytes.size()});
unsigned int found = 0;
for (auto it = zbi.begin(); it != zbi.end(); ++it) {
if ((*it).header->type == ZBI_TYPE_SECURE_ENTROPY) {
auto data = (*it).payload;
if (data.size() < PRNG::kMinEntropy) {
printf("ZBI_TYPE_SECURE_ENTROPY item at offset %#x too small: %zu < %zu\n",
it.item_offset(), data.size(), static_cast<size_t>(PRNG::kMinEntropy));
} else {
kGlobalPrng->AddEntropy(data.data(), data.size());
mandatory_memset(data.data(), 0, data.size());
LTRACEF("Collected %zu bytes of entropy from a ZBI Item\n", data.size());
auto result = zbi.EditHeader(it, {.type = ZBI_TYPE_DISCARD});
ZX_ASSERT(result.is_ok());
++found;
}
}
}
zbi.ignore_error();
return found;
}
// Returns true if the kernel cmdline provided at least PRNG::kMinEntropy bytes
// of entropy, and false otherwise.
//
// TODO(security): Remove this in favor of virtio-rng once it is available and
// we decide we don't need it for getting entropy from elsewhere.
static bool IntegrateCmdlineEntropy() {
ktl::string_view entropy{gBootOptions->entropy_mixin};
if (entropy.empty()) {
return false;
}
constexpr size_t kMaxEntropyArgumentLen = 128;
const size_t hex_len = ktl::min(entropy.size(), kMaxEntropyArgumentLen);
entropy = entropy.substr(hex_len);
for (char c : entropy) {
if (!isxdigit(c)) {
panic("Invalid entropy string %.*s: '%c' is not an ASCII hex digit\n",
static_cast<int>(entropy.size()), entropy.data(), c);
}
}
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const uint8_t*>(entropy.data()), entropy.size(), digest);
kGlobalPrng->AddEntropy(digest, sizeof(digest));
const size_t entropy_added = ktl::max(hex_len / 2, sizeof(digest));
LTRACEF("Collected %zu bytes of entropy from the kernel cmdline.\n", entropy_added);
return (entropy_added >= PRNG::kMinEntropy);
}
// Returns true on success, false on failure.
static bool SeedFrom(entropy::Collector* collector) {
uint8_t buf[PRNG::kMinEntropy] = {0};
size_t remaining = collector->BytesNeeded(8 * PRNG::kMinEntropy);
#if LOCAL_TRACE
{
char name[ZX_MAX_NAME_LEN];
collector->get_name(name, sizeof(name));
LTRACEF("About to collect %zu bytes of entropy from '%s'.\n", remaining, name);
}
#endif
while (remaining > 0) {
size_t result = collector->DrawEntropy(buf, ktl::min(sizeof(buf), remaining));
if (result == 0) {
LTRACEF(
"Collected 0 bytes; aborting. "
"There were %zu bytes remaining to collect.\n",
remaining);
return false;
}
kGlobalPrng->AddEntropy(buf, result);
mandatory_memset(buf, 0, sizeof(buf));
remaining -= result;
}
LTRACEF("Successfully collected entropy.\n");
return true;
}
// Instantiates the global PRNG (in non-thread-safe mode) and seeds it.
static void EarlyBootSeed(uint level) {
ASSERT(kGlobalPrng == nullptr);
// Before doing anything else, test our entropy collector. This is
// explicitly called here rather than in another init hook to ensure
// ordering (at level LK_INIT_LEVEL_PLATFORM_EARLY + 1, but before the rest
// of EarlyBootSeed).
entropy::EarlyBootTest();
// Statically allocate an array of bytes to put the PRNG into. We do this
// to control when the PRNG constructor is called.
// TODO(security): This causes the PRNG state to be in a fairly predictable
// place. Some aspects of KASLR will help with this, but we may
// additionally want to remap where this is later.
alignas(alignof(PRNG)) static uint8_t prng_space[sizeof(PRNG)];
kGlobalPrng = new (&prng_space) PRNG(nullptr, 0, PRNG::NonThreadSafeTag());
unsigned int successful = 0; // number of successful entropy sources
entropy::Collector* collector = nullptr;
if (entropy::HwRngCollector::GetInstance(&collector) == ZX_OK && SeedFrom(collector)) {
successful++;
} else if (gBootOptions->cprng_seed_require_hw_rng) {
panic("Failed to seed PRNG from required entropy source: hw-rng\n");
}
if (entropy::JitterentropyCollector::GetInstance(&collector) == ZX_OK && SeedFrom(collector)) {
successful++;
} else if (gBootOptions->cprng_seed_require_jitterentropy) {
panic("Failed to seed PRNG from required entropy source: jitterentropy\n");
}
unsigned int zbi_items = IntegrateZbiEntropy();
successful += zbi_items;
if (IntegrateCmdlineEntropy()) {
successful++;
} else if (zbi_items == 0 && gBootOptions->cprng_seed_require_cmdline) {
panic("Failed to seed PRNG from required entropy source: cmdline\n");
}
if (successful == 0) {
printf(
"WARNING: System has insufficient randomness. It is completely "
"unsafe to use this system for any cryptographic applications."
"\n");
// TODO(security): *CRITICAL* This is a fallback for systems without RNG
// hardware that we should remove and attempt to do better. If this
// fallback is used, it breaks all cryptography used on the system.
// *CRITICAL*
uint8_t buf[PRNG::kMinEntropy] = {0};
kGlobalPrng->AddEntropy(buf, sizeof(buf));
return;
} else {
LTRACEF("Successfully collected entropy from %u sources.\n", successful);
}
}
// Migrate the global PRNG to enter thread-safe mode.
static void BecomeThreadSafe(uint level) { GetInstance()->BecomeThreadSafe(); }
// PRNG reseeding loop.
static int ReseedPRNG(void* arg) {
for (;;) {
Thread::Current::SleepRelative(ZX_SEC(30));
unsigned int successful = 0; // number of successful entropy sources
entropy::Collector* collector = nullptr;
// Reseed using HW RNG and jitterentropy;
if (entropy::HwRngCollector::GetInstance(&collector) == ZX_OK && SeedFrom(collector)) {
successful++;
} else if (gBootOptions->cprng_reseed_require_hw_rng) {
panic("Failed to reseed PRNG from required entropy source: hw-rng\n");
}
if (entropy::JitterentropyCollector::GetInstance(&collector) == ZX_OK && SeedFrom(collector)) {
successful++;
} else if (gBootOptions->cprng_reseed_require_jitterentropy) {
panic("Failed to reseed PRNG from required entropy source: jitterentropy\n");
}
if (successful == 0) {
kGlobalPrng->SelfReseed();
LTRACEF("Reseed PRNG with no new entropy source\n");
} else {
LTRACEF("Successfully reseed PRNG from %u sources.\n", successful);
}
}
return 0;
}
// Start a thread to reseed PRNG.
static void StartReseedThread(uint level) {
Thread* t = Thread::Create("prng-reseed", ReseedPRNG, nullptr, HIGHEST_PRIORITY);
t->DetachAndResume();
}
} // namespace GlobalPRNG
} // namespace crypto
LK_INIT_HOOK(global_prng_seed, crypto::GlobalPRNG::EarlyBootSeed, LK_INIT_LEVEL_PLATFORM_EARLY + 1)
LK_INIT_HOOK(global_prng_thread_safe, crypto::GlobalPRNG::BecomeThreadSafe,
LK_INIT_LEVEL_THREADING - 1)
LK_INIT_HOOK(global_prng_reseed, crypto::GlobalPRNG::StartReseedThread, LK_INIT_LEVEL_THREADING)