blob: 5295821146a00cd89552eee06e2010a187a1a0d3 [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 <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#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/move.h>
#include <ktl/span.h>
#include <ktl/string_view.h>
#include <lk/init.h>
#include <openssl/sha.h>
#include <phys/handoff.h>
#include <ktl/enforce.h>
#define LOCAL_TRACE 0
namespace crypto {
namespace global_prng {
namespace {
Prng* g_prng_instance = nullptr;
// Returns true on success, false on failure.
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;
}
g_prng_instance->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.
void EarlyBootSeed(uint level) {
ASSERT(g_prng_instance == 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)];
// number of successful entropy sources
unsigned int successful = 0;
g_prng_instance = new (&prng_space) Prng(nullptr, 0, crypto::Prng::NonThreadSafeTag{});
// All validation from zbi item and commandline item is performed on phys, so
// this instance of entropy pool, is guaranteed to meet the minimum requirements
// for the current boot options, or we would have panic'd already.
ZX_ASSERT(!gBootOptions->cprng_seed_require_cmdline || gPhysHandoff->entropy_pool.has_value());
if (gPhysHandoff->entropy_pool) {
// |pool|'s destructor will wipe the stack.
auto pool = ktl::move(gPhysHandoff->entropy_pool.value());
g_prng_instance->AddEntropy(pool.contents().data(), pool.contents().size());
successful++;
}
entropy::Collector* collector = nullptr;
if (!gBootOptions->cprng_disable_hw_rng &&
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 (!gBootOptions->cprng_disable_jitterentropy &&
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");
}
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};
g_prng_instance->AddEntropy(buf, sizeof(buf));
return;
} else {
LTRACEF("Successfully collected entropy from %u sources.\n", successful);
}
}
// Migrate the global PRNG to enter thread-safe mode.
void BecomeThreadSafe(uint level) { GetInstance()->BecomeThreadSafe(); }
// Collect entropy and add it to the cprng.
void ReseedPRNG() {
unsigned int successful = 0; // number of successful entropy sources
entropy::Collector* collector = nullptr;
// Reseed using HW RNG and jitterentropy;
if (!gBootOptions->cprng_disable_hw_rng &&
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 (!gBootOptions->cprng_disable_jitterentropy &&
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) {
g_prng_instance->SelfReseed();
LTRACEF("Reseed PRNG with no new entropy source\n");
} else {
LTRACEF("Successfully reseed PRNG from %u sources.\n", successful);
}
}
int ReseedLoop(void* arg) {
for (;;) {
Thread::Current::SleepRelative(ZX_SEC(30));
ReseedPRNG();
}
return 0;
}
// Start a thread to reseed PRNG.
void StartReseedThread(uint level) {
// Force a reseed before returning from the init hook.
// We have no guarantees when the thread will be scheduled and run.
// TODO(fxbug.dev/82810): Make this synchronous reseed faster by removing
// JitterEntropy reseed, as we already seeded from it in EarlyBoot.
ReseedPRNG();
Thread* t = Thread::Create("prng-reseed", ReseedLoop, nullptr, HIGHEST_PRIORITY);
t->DetachAndResume();
}
} // namespace
Prng* GetInstance() {
ASSERT(g_prng_instance);
return g_prng_instance;
}
} // namespace global_prng
} // namespace crypto
// intel hw_rng init hook is at PLATFORM_EARLY+1
// make sure we start after that so we can use it for the early seed.
LK_INIT_HOOK(global_prng_seed, crypto::global_prng::EarlyBootSeed, LK_INIT_LEVEL_PLATFORM_EARLY + 2)
LK_INIT_HOOK(global_prng_thread_safe, crypto::global_prng::BecomeThreadSafe,
LK_INIT_LEVEL_THREADING - 1)
// Reseed the CPRNG right before entering userspace.
LK_INIT_HOOK(global_prng_reseed, crypto::global_prng::StartReseedThread, LK_INIT_LEVEL_USER - 1)