diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 93d459521..383696e3a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -16,6 +16,7 @@ xous-kernel = { package = "xous", version = "0.9.57", features = [ ] } utralib = { version = "0.1.24", optional = true, default_features = false } rand_chacha = { version = "0.3.1", optional = true, default_features = false } +cramium-hal = { path = "../libs/cramium-hal", optional = true } [target.'cfg(any(windows,unix))'.dev-dependencies] xous-kernel = { package = "xous", version = "0.9.57", features = [ @@ -37,7 +38,7 @@ xous-kernel = { package = "xous", version = "0.9.57", features = ["v2p"] } critical-section = "1.1.1" [features] -cramium-soc = ["utralib/cramium-soc"] +cramium-soc = ["utralib/cramium-soc", "cramium-hal", "rand_chacha"] cramium-fpga = ["utralib/cramium-fpga"] atsama5d27 = ["utralib/atsama5d27"] precursor = ["utralib/precursor"] @@ -54,9 +55,6 @@ v2p = ["xous-kernel/v2p"] # patches for simulation targets ONLY. Applying these flags will result in totally broken security. hwsim = [] -fake-rng = [ - "rand_chacha", -] # works around lack of a real TRNG in the full-chip sim for now. This should go away for production. # default = ["print-panics", "debug-print", "wrap-print"] default = ["print-panics"] diff --git a/kernel/src/platform/cramium/rand.rs b/kernel/src/platform/cramium/rand.rs index c7d3c0d48..d0d35c2bd 100644 --- a/kernel/src/platform/cramium/rand.rs +++ b/kernel/src/platform/cramium/rand.rs @@ -2,88 +2,53 @@ // SPDX-FileCopyrightText: 2022 Foundation Devices, Inc. // SPDX-License-Identifier: Apache-2.0 -#[cfg(not(feature = "fake-rng"))] -use utralib::generated::*; -#[cfg(not(feature = "fake-rng"))] +use cramium_hal::sce; +use rand_chacha::rand_core::RngCore; +use rand_chacha::rand_core::SeedableRng; +use rand_chacha::ChaCha8Rng; use xous_kernel::{MemoryFlags, MemoryType, PID}; -#[cfg(not(feature = "fake-rng"))] use crate::mem::MemoryManager; /// The manually chosen virtual address has to be in the top 4MiB as it is the /// only page shared among all processes. /// /// See https://github.com/betrusted-io/xous-core/blob/master/docs/memory.md -#[cfg(not(feature = "fake-rng"))] pub const TRNG_KERNEL_ADDR: usize = 0xffce_0000; -#[cfg(not(feature = "fake-rng"))] -pub static mut TRNG_KERNEL: Option = None; -#[cfg(feature = "fake-rng")] +pub static mut TRNG_KERNEL: Option = None; use core::sync::atomic::{AtomicU32, Ordering}; -#[cfg(feature = "fake-rng")] -static LOCAL_RNG_STATE: AtomicU32 = AtomicU32::new(2); - -#[cfg(not(feature = "fake-rng"))] -pub struct TrngKernel { - pub trng_kernel_csr: CSR, -} - -#[cfg(not(feature = "fake-rng"))] -impl TrngKernel { - pub fn new(addr: usize) -> TrngKernel { TrngKernel { trng_kernel_csr: CSR::new(addr as *mut u32) } } - - pub fn init(&mut self) { - if false { - // raw random path - left in for debugging urandom, can strip out later - while self.trng_kernel_csr.rf(utra::trng_kernel::STATUS_AVAIL) == 0 {} - // discard the first entry, as it is always 0x0000_0000 - // this is because the read register is a pipeline stage behind the FIFO - // once the pipeline has been filled, there is no need to prime it again. - self.trng_kernel_csr.rf(utra::trng_kernel::DATA_DATA); - } else { - // urandom path (recommended) - // simulations show this isn't strictly necessary, but I prefer to have it - // just in case a subtle bug in the reset logic leaves something deterministic - // in the connecting logic: the simulation coverage stops at the edge of the TRNG block. - for _ in 0..4 { - // wait until the urandom port is initialized - while self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_VALID_URANDOM_VALID) == 0 {} - // pull a dummy piece of data - self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_URANDOM); - } - } - } - - pub fn get_u32(&mut self) -> u32 { - if false { - // raw random path - while self.trng_kernel_csr.rf(utra::trng_kernel::STATUS_AVAIL) == 0 {} - self.trng_kernel_csr.rf(utra::trng_kernel::DATA_DATA) - } else { - // urandom path (recommended) - while self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_VALID_URANDOM_VALID) == 0 {} - self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_URANDOM) - } - } -} +// these values are overwritten on boot with something out of the TRNG. +static LOCAL_RNG_STATE_LSB: AtomicU32 = AtomicU32::new(0); +static LOCAL_RNG_STATE_MSB: AtomicU32 = AtomicU32::new(0); /// Initialize TRNG driver. /// /// Needed so that the kernel can allocate names. -#[cfg(not(feature = "fake-rng"))] +/// This driver "owns" the hardware TRNG on the chip. Because there aren't two +/// TRNG ports on the Cramium implementation, it means that userspace applications +/// have to extract random numbers from the kernel through the SID request mechanism. +/// This is ostensibly harmless, if everything is implemented well, but if there is +/// a problem it does mean that the state of the kernel TRNG is disclosed to userspace. +/// What can you do though, this chip just has one TRNG. +/// +/// The final implementation for user applications is recommended to have a supplementary +/// noise source in addition to the on-chip noise source, so that user applications +/// combine both the feed from the kernel and the supplementary source to seed a CSPRNG. +/// +/// This particular application doesn't trust the on-chip TRNG so much: it takes +/// values from the TRNG and folds them into a seed pool that is then groomed by +/// a ChaCha8 RNG CSPRNG. Every request for a random number folds another 32 bits +/// from the TRNG into the CSPRNG pool, so even if the TRNG is fairly poor, quite +/// rapidly the pool of numbers should diverge. However, from the kernel's perspective, +/// it will function correctly even if the CSPRNG is fully deterministic, it just isn't +/// secure against attackers trying to guess a server address. + pub fn init() { // Map the TRNG so that we can allocate names - // hardware guarantees that: - // - TRNG will automatically power on - // - Both TRNGs are enabled, with conservative defaults - // - Kernel FIFO will fill with TRNGs such that at least the next 512 calls to get_u32() will succeed - // without delay - // - The kernel will start a TRNG server - // - All further security decisions and policies are 100% delegated to this new server. MemoryManager::with_mut(|memory_manager| { memory_manager .map_range( - utra::trng_kernel::HW_TRNG_KERNEL_BASE as *mut u8, + utralib::generated::HW_TRNG_BASE as *mut u8, (TRNG_KERNEL_ADDR & !4095) as *mut u8, 4096, PID::new(1).unwrap(), @@ -93,34 +58,46 @@ pub fn init() { .expect("unable to map TRNG_KERNEL") }); - let mut trng_kernel = TrngKernel::new(TRNG_KERNEL_ADDR); - trng_kernel.init(); + let mut trng_kernel = sce::trng::Trng::new(TRNG_KERNEL_ADDR); + trng_kernel.setup_raw_generation(256); + // setup the initial seeds with stuff out of the TRNG. + LOCAL_RNG_STATE_LSB.store(trng_kernel.get_u32().expect("TRNG error"), Ordering::SeqCst); + LOCAL_RNG_STATE_MSB.store(trng_kernel.get_u32().expect("TRNG error"), Ordering::SeqCst); + + // move the trng_kernel object into the static mut state unsafe { TRNG_KERNEL = Some(trng_kernel); } + + // now run the CSPRNG a few cycles to fold more entropy into the pool before we start + // to use it for reals. 16 iterations will extract 512 bits out of the TRNG, so even + // if it is somewhat degraded, we'll be in good shape... + for _ in 0..16 { + let _ = get_u32(); + } } /// Retrieve random `u32`. -#[cfg(not(feature = "fake-rng"))] pub fn get_u32() -> u32 { - unsafe { TRNG_KERNEL.as_mut().expect("TRNG_KERNEL driver not initialized").get_u32() } -} - -/// Initialize TRNG driver. -/// -/// Needed so that the kernel can allocate names. -#[cfg(feature = "fake-rng")] -pub fn init() {} - -#[cfg(feature = "fake-rng")] -pub fn get_u32() -> u32 { - use rand_chacha::rand_core::RngCore; - use rand_chacha::rand_core::SeedableRng; - use rand_chacha::ChaCha8Rng; - - let mut rng = ChaCha8Rng::seed_from_u64(LOCAL_RNG_STATE.load(Ordering::SeqCst) as u64); - let r = rng.next_u32(); - LOCAL_RNG_STATE.store(rng.next_u64() as u32, Ordering::SeqCst); - r + let mut state = LOCAL_RNG_STATE_LSB.load(Ordering::SeqCst) as u64 + | (LOCAL_RNG_STATE_MSB.load(Ordering::SeqCst) as u64) << 32; + + // XOR in 32 bits of entropy from the HW TRNG pool. + state ^= unsafe { + TRNG_KERNEL + .as_mut() + .expect("TRNG_KERNEL driver not initialized") + .get_u32() + .expect("Error in random number generation") + } as u64; + + let mut rng = ChaCha8Rng::seed_from_u64(state); + let next_state = rng.next_u64(); + // next state is extracted before the returned value, to reduce state + // leakage outside of the RNG. + let ret_val = rng.next_u32(); + LOCAL_RNG_STATE_LSB.store(next_state as u32, Ordering::SeqCst); + LOCAL_RNG_STATE_MSB.store((next_state >> 32) as u32, Ordering::SeqCst); + ret_val }