diff --git a/Cargo.lock b/Cargo.lock index 475bb1b8208ae636c12ed705b1af981ebc3159d6..19d9a7ee3c5e67f21ef1c843edcef30dfc543910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,7 @@ dependencies = [ "num-traits", "pci-ids", "qemu-exit", + "rand_chacha", "scopeguard", "shell-words", "smoltcp", @@ -464,6 +465,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.47" @@ -497,6 +504,16 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" diff --git a/Cargo.toml b/Cargo.toml index 3039a82b63f7c3e4cff7df6ce1e54ce1a87dc605..1b677020330e545c64dfff49ead5c5064de282b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ pci-ids = { version = "0.2", optional = true } scopeguard = { version = "1.1", default-features = false } shell-words = { version = "1.1", default-features = false } qemu-exit = "3.0" +rand_chacha = { version = "0.3", default-features = false } futures-lite = { version = "1.11", default-features = false, optional = true } async-task = { version = "4.3", default-features = false, optional = true } diff --git a/src/arch/aarch64/kernel/processor.rs b/src/arch/aarch64/kernel/processor.rs index 84011f8f8a88e0b5a76a97be2762cb281f2b4f12..e28dc9e3565b0ec4941492b5ac293fe73e575dc9 100644 --- a/src/arch/aarch64/kernel/processor.rs +++ b/src/arch/aarch64/kernel/processor.rs @@ -23,11 +23,7 @@ impl FPUState { } } -pub fn generate_random_number32() -> Option<u32> { - None -} - -pub fn generate_random_number64() -> Option<u64> { +pub fn seed_entropy() -> Option<[u8; 32]> { None } diff --git a/src/arch/x86_64/kernel/processor.rs b/src/arch/x86_64/kernel/processor.rs index bb483504e8cd47d49150160516592597833eba26..eb7b9da0c4c40e6696f09504186955c6a1482693 100644 --- a/src/arch/x86_64/kernel/processor.rs +++ b/src/arch/x86_64/kernel/processor.rs @@ -2,8 +2,7 @@ use core::arch::asm; use core::arch::x86_64::{ - __rdtscp, _fxrstor, _fxsave, _mm_lfence, _rdrand32_step, _rdrand64_step, _rdtsc, _xrstor, - _xsave, + __rdtscp, _fxrstor, _fxsave, _mm_lfence, _rdseed64_step, _rdtsc, _xrstor, _xsave, }; use core::convert::Infallible; use core::hint::spin_loop; @@ -49,7 +48,7 @@ struct Features { linear_address_bits: u8, supports_1gib_pages: bool, supports_avx: bool, - supports_rdrand: bool, + supports_rdseed: bool, supports_tsc_deadline: bool, supports_x2apic: bool, supports_xsave: bool, @@ -79,7 +78,7 @@ static FEATURES: Lazy<Features> = Lazy::new(|| { linear_address_bits: processor_capacity_info.linear_address_bits(), supports_1gib_pages: extend_processor_identifiers.has_1gib_pages(), supports_avx: feature_info.has_avx(), - supports_rdrand: feature_info.has_rdrand(), + supports_rdseed: extended_feature_info.has_rdseed(), supports_tsc_deadline: feature_info.has_tsc_deadline(), supports_x2apic: feature_info.has_x2apic(), supports_xsave: feature_info.has_xsave(), @@ -893,32 +892,27 @@ pub fn print_information() { infofooter!(); } -pub fn generate_random_number32() -> Option<u32> { - unsafe { - if FEATURES.supports_rdrand { - let mut value: u32 = 0; - - for _ in 0..RDRAND_RETRY_LIMIT { - if _rdrand32_step(&mut value) == 1 { - return Some(value); - } +pub fn seed_entropy() -> Option<[u8; 32]> { + let mut buf = [0; 32]; + if FEATURES.supports_rdseed { + for word in buf.chunks_mut(8) { + let mut value = 0; + + // Some RDRAND implementations on AMD CPUs have had bugs where the carry + // flag was incorrectly set without there actually being a random value + // available. Even though no bugs are known for RDSEED, we should not + // consider the default values random for extra security. + while unsafe { _rdseed64_step(&mut value) != 1 } || value == 0 || value == !0 { + // Spin as per the recommendation in the + // IntelĀ® Digital Random Number Generator (DRNG) implementation guide + spin_loop(); } - } - None - } -} -pub fn generate_random_number64() -> Option<u64> { - unsafe { - if FEATURES.supports_rdrand { - let mut value: u64 = 0; - - for _ in 0..RDRAND_RETRY_LIMIT { - if _rdrand64_step(&mut value) == 1 { - return Some(value); - } - } + word.copy_from_slice(&value.to_ne_bytes()); } + + Some(buf) + } else { None } } diff --git a/src/entropy.rs b/src/entropy.rs new file mode 100644 index 0000000000000000000000000000000000000000..08d55da06e32fd2241148a6ab12f4548d79516fd --- /dev/null +++ b/src/entropy.rs @@ -0,0 +1,53 @@ +//! Cryptographically secure random data generation. +//! +//! This currently uses a ChaCha-based generator (the same one Linux uses!) seeded +//! with random data provided by the processor. + +use hermit_sync::InterruptTicketMutex; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; + +use crate::arch::kernel::processor::{get_timer_ticks, seed_entropy}; +use crate::errno::ENOSYS; + +// Reseed every second for increased security while maintaining the performance of +// the PRNG. +const RESEED_INTERVAL: u64 = 1000000; + +bitflags! { + pub struct Flags: u32 {} +} + +struct Pool { + rng: ChaCha20Rng, + last_reseed: u64, +} + +static POOL: InterruptTicketMutex<Option<Pool>> = InterruptTicketMutex::new(None); + +/// Fills `buf` with random data, respecting the options in `flags`. +/// +/// Returns the number of bytes written or `-ENOSYS` if the system does not support +/// random data generation. +pub fn read(buf: &mut [u8], _flags: Flags) -> isize { + let pool = &mut *POOL.lock(); + let now = get_timer_ticks(); + let pool = match pool { + Some(pool) if now.saturating_sub(pool.last_reseed) <= RESEED_INTERVAL => pool, + pool => { + if let Some(seed) = seed_entropy() { + pool.insert(Pool { + rng: ChaCha20Rng::from_seed(seed), + last_reseed: now, + }) + } else { + return -ENOSYS as isize; + } + } + }; + + pool.rng.fill_bytes(buf); + // Slice lengths are always <= isize::MAX so this return value cannot conflict + // with error numbers. + buf.len() as isize +} diff --git a/src/lib.rs b/src/lib.rs index a5c7ae99800aa5474ef4eeabf5d62bfc22bacc01..39c47acd8e5bdd7b6af4447e07af42ba810a453c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,7 @@ mod arch; mod config; mod console; mod drivers; +mod entropy; mod env; pub mod errno; pub(crate) mod fd; diff --git a/src/macros.rs b/src/macros.rs index 5c65c6829a4308cc4cdf724bf41095993a5c0bca..8697ace3cf8642b953918713dde5ad5e64a18830 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -42,18 +42,6 @@ macro_rules! dbg { }; } -macro_rules! try_sys { - ($expr:expr $(,)?) => { - match $expr { - ::core::result::Result::Ok(val) => val, - ::core::result::Result::Err(err) => { - error!("{err}"); - return -1; - } - } - }; -} - /// Runs `f` on the kernel stack. /// /// All arguments and return values have to fit into registers: diff --git a/src/syscalls/random.rs b/src/syscalls/entropy.rs similarity index 50% rename from src/syscalls/random.rs rename to src/syscalls/entropy.rs index 4ee3b7937ce1640896fd7c4cb6ebad49527bfe22..d9c679ab74136b259a6d0272eae46f0428009635 100644 --- a/src/syscalls/random.rs +++ b/src/syscalls/entropy.rs @@ -1,6 +1,11 @@ +use core::mem::size_of; +use core::slice; + use hermit_sync::TicketMutex; use crate::arch; +use crate::entropy::{self, Flags}; +use crate::errno::EINVAL; static PARK_MILLER_LEHMER_SEED: TicketMutex<u32> = TicketMutex::new(0); const RAND_MAX: u64 = 2_147_483_647; @@ -12,42 +17,74 @@ fn generate_park_miller_lehmer_random_number() -> u32 { random } -unsafe extern "C" fn __sys_rand32(value: *mut u32) -> i32 { - let rand = try_sys!(arch::processor::generate_random_number32().ok_or("sys_rand32 failed")); - unsafe { - value.write(rand); - } - 0 -} +unsafe extern "C" fn __sys_read_entropy(buf: *mut u8, len: usize, flags: u32) -> isize { + let Some(flags) = Flags::from_bits(flags) else { return -EINVAL as isize }; -unsafe extern "C" fn __sys_rand64(value: *mut u64) -> i32 { - let rand = try_sys!(arch::processor::generate_random_number64().ok_or("sys_rand64 failed")); - unsafe { - value.write(rand); - } - 0 + let buf = unsafe { + // Cap the number of bytes to be read at a time to isize::MAX to uphold + // the safety guarantees of `from_raw_parts`. + let len = usize::min(len, isize::MAX as usize); + buf.write_bytes(0, len); + slice::from_raw_parts_mut(buf, len) + }; + + entropy::read(buf, flags) } -extern "C" fn __sys_rand() -> u32 { - generate_park_miller_lehmer_random_number() +/// Fill `len` bytes in `buf` with cryptographically secure random data. +/// +/// Returns either the number of bytes written to buf (a positive value) or +/// * `-EINVAL` if `flags` contains unknown flags. +/// * `-ENOSYS` if the system does not support random data generation. +#[no_mangle] +pub unsafe extern "C" fn sys_read_entropy(buf: *mut u8, len: usize, flags: u32) -> isize { + kernel_function!(__sys_read_entropy(buf, len, flags)) } /// Create a cryptographicly secure 32bit random number with the support of /// the underlying hardware. If the required hardware isn't available, -/// the function returns `None`. +/// the function returns `-1`. #[cfg(not(feature = "newlib"))] #[no_mangle] pub unsafe extern "C" fn sys_secure_rand32(value: *mut u32) -> i32 { - kernel_function!(__sys_rand32(value)) + let mut buf = value.cast(); + let mut len = size_of::<u32>(); + while len != 0 { + let res = unsafe { sys_read_entropy(buf, len, 0) }; + if res < 0 { + return -1; + } + + buf = unsafe { buf.add(res as usize) }; + len -= res as usize; + } + + 0 } /// Create a cryptographicly secure 64bit random number with the support of /// the underlying hardware. If the required hardware isn't available, -/// the function returns `None`. +/// the function returns -1. #[cfg(not(feature = "newlib"))] #[no_mangle] pub unsafe extern "C" fn sys_secure_rand64(value: *mut u64) -> i32 { - kernel_function!(__sys_rand64(value)) + let mut buf = value.cast(); + let mut len = size_of::<u64>(); + while len != 0 { + let res = unsafe { sys_read_entropy(buf, len, 0) }; + if res < 0 { + return -1; + } + + buf = unsafe { buf.add(res as usize) }; + len -= res as usize; + } + + 0 +} + +extern "C" fn __sys_rand() -> u32 { + generate_park_miller_lehmer_random_number() } /// The function computes a sequence of pseudo-random integers @@ -68,7 +105,7 @@ pub extern "C" fn sys_srand(seed: u32) { kernel_function!(__sys_srand(seed)) } -pub(crate) fn random_init() { +pub(crate) fn init_entropy() { let seed: u32 = arch::processor::get_timestamp() as u32; *PARK_MILLER_LEHMER_SEED.lock() = seed; diff --git a/src/syscalls/mod.rs b/src/syscalls/mod.rs index 36e18331ea8986909f7a3742b3bc6f296384b8ad..a1cd3320cdf51a4b122cc64a05c51cce388a0bb3 100644 --- a/src/syscalls/mod.rs +++ b/src/syscalls/mod.rs @@ -5,9 +5,9 @@ use hermit_sync::InterruptTicketMutex; use hermit_sync::Lazy; pub use self::condvar::*; +pub use self::entropy::*; pub use self::futex::*; pub use self::processor::*; -pub use self::random::*; pub use self::recmutex::*; pub use self::semaphore::*; pub use self::spinlock::*; @@ -21,6 +21,7 @@ use crate::syscalls::interfaces::SyscallInterface; use crate::{__sys_free, __sys_malloc, __sys_realloc}; mod condvar; +mod entropy; pub(crate) mod fs; mod futex; mod interfaces; @@ -29,7 +30,6 @@ mod lwip; #[cfg(all(feature = "tcp", not(feature = "newlib")))] mod net; mod processor; -mod random; mod recmutex; mod semaphore; mod spinlock; @@ -68,7 +68,7 @@ pub(crate) fn init() { // Perform interface-specific initialization steps. SYS.init(); - random_init(); + init_entropy(); #[cfg(feature = "newlib")] sbrk_init(); }