Skip to content

Commit f61b60e

Browse files
bors[bot]joboet
andauthored
Merge #640
640: Reimplement random data generation, add `read_entropy` syscall r=stlankes a=joboet Fixes #143 by reimplementing random data generation using a [ChaCha-based RNG](https://docs.rs/rand_chacha/0.3.1/rand_chacha/struct.ChaCha20Rng.html) continuously reseeded using the `RDSEED` instruction. This should provide better security, as `RDRAND` is known to have hardware bugs. Also adds a new buffer-based syscall, `read_entropy`, which better fits the usecase of crates like [`getrandom`](https://github.com/rust-random/getrandom). The old `secure_rand*` and `rand` syscalls should probably be removed at some point, but I do not know the Hermit stability policy and therefore have not done this in this PR. Co-authored-by: joboet <jonasboettiger@icloud.com>
2 parents 7add968 + 58860a9 commit f61b60e

File tree

9 files changed

+154
-67
lines changed

9 files changed

+154
-67
lines changed

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pci-ids = { version = "0.2", optional = true }
8484
scopeguard = { version = "1.1", default-features = false }
8585
shell-words = { version = "1.1", default-features = false }
8686
qemu-exit = "3.0"
87+
rand_chacha = { version = "0.3", default-features = false }
8788
futures-lite = { version = "1.11", default-features = false, optional = true }
8889
async-task = { version = "4.3", default-features = false, optional = true }
8990

src/arch/aarch64/kernel/processor.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ impl FPUState {
2323
}
2424
}
2525

26-
pub fn generate_random_number32() -> Option<u32> {
27-
None
28-
}
29-
30-
pub fn generate_random_number64() -> Option<u64> {
26+
pub fn seed_entropy() -> Option<[u8; 32]> {
3127
None
3228
}
3329

src/arch/x86_64/kernel/processor.rs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
use core::arch::asm;
44
use core::arch::x86_64::{
5-
__rdtscp, _fxrstor, _fxsave, _mm_lfence, _rdrand32_step, _rdrand64_step, _rdtsc, _xrstor,
6-
_xsave,
5+
__rdtscp, _fxrstor, _fxsave, _mm_lfence, _rdseed64_step, _rdtsc, _xrstor, _xsave,
76
};
87
use core::convert::Infallible;
98
use core::hint::spin_loop;
@@ -49,7 +48,7 @@ struct Features {
4948
linear_address_bits: u8,
5049
supports_1gib_pages: bool,
5150
supports_avx: bool,
52-
supports_rdrand: bool,
51+
supports_rdseed: bool,
5352
supports_tsc_deadline: bool,
5453
supports_x2apic: bool,
5554
supports_xsave: bool,
@@ -79,7 +78,7 @@ static FEATURES: Lazy<Features> = Lazy::new(|| {
7978
linear_address_bits: processor_capacity_info.linear_address_bits(),
8079
supports_1gib_pages: extend_processor_identifiers.has_1gib_pages(),
8180
supports_avx: feature_info.has_avx(),
82-
supports_rdrand: feature_info.has_rdrand(),
81+
supports_rdseed: extended_feature_info.has_rdseed(),
8382
supports_tsc_deadline: feature_info.has_tsc_deadline(),
8483
supports_x2apic: feature_info.has_x2apic(),
8584
supports_xsave: feature_info.has_xsave(),
@@ -893,32 +892,27 @@ pub fn print_information() {
893892
infofooter!();
894893
}
895894

896-
pub fn generate_random_number32() -> Option<u32> {
897-
unsafe {
898-
if FEATURES.supports_rdrand {
899-
let mut value: u32 = 0;
900-
901-
for _ in 0..RDRAND_RETRY_LIMIT {
902-
if _rdrand32_step(&mut value) == 1 {
903-
return Some(value);
904-
}
895+
pub fn seed_entropy() -> Option<[u8; 32]> {
896+
let mut buf = [0; 32];
897+
if FEATURES.supports_rdseed {
898+
for word in buf.chunks_mut(8) {
899+
let mut value = 0;
900+
901+
// Some RDRAND implementations on AMD CPUs have had bugs where the carry
902+
// flag was incorrectly set without there actually being a random value
903+
// available. Even though no bugs are known for RDSEED, we should not
904+
// consider the default values random for extra security.
905+
while unsafe { _rdseed64_step(&mut value) != 1 } || value == 0 || value == !0 {
906+
// Spin as per the recommendation in the
907+
// Intel® Digital Random Number Generator (DRNG) implementation guide
908+
spin_loop();
905909
}
906-
}
907-
None
908-
}
909-
}
910910

911-
pub fn generate_random_number64() -> Option<u64> {
912-
unsafe {
913-
if FEATURES.supports_rdrand {
914-
let mut value: u64 = 0;
915-
916-
for _ in 0..RDRAND_RETRY_LIMIT {
917-
if _rdrand64_step(&mut value) == 1 {
918-
return Some(value);
919-
}
920-
}
911+
word.copy_from_slice(&value.to_ne_bytes());
921912
}
913+
914+
Some(buf)
915+
} else {
922916
None
923917
}
924918
}

src/entropy.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! Cryptographically secure random data generation.
2+
//!
3+
//! This currently uses a ChaCha-based generator (the same one Linux uses!) seeded
4+
//! with random data provided by the processor.
5+
6+
use hermit_sync::InterruptTicketMutex;
7+
use rand_chacha::rand_core::{RngCore, SeedableRng};
8+
use rand_chacha::ChaCha20Rng;
9+
10+
use crate::arch::kernel::processor::{get_timer_ticks, seed_entropy};
11+
use crate::errno::ENOSYS;
12+
13+
// Reseed every second for increased security while maintaining the performance of
14+
// the PRNG.
15+
const RESEED_INTERVAL: u64 = 1000000;
16+
17+
bitflags! {
18+
pub struct Flags: u32 {}
19+
}
20+
21+
struct Pool {
22+
rng: ChaCha20Rng,
23+
last_reseed: u64,
24+
}
25+
26+
static POOL: InterruptTicketMutex<Option<Pool>> = InterruptTicketMutex::new(None);
27+
28+
/// Fills `buf` with random data, respecting the options in `flags`.
29+
///
30+
/// Returns the number of bytes written or `-ENOSYS` if the system does not support
31+
/// random data generation.
32+
pub fn read(buf: &mut [u8], _flags: Flags) -> isize {
33+
let pool = &mut *POOL.lock();
34+
let now = get_timer_ticks();
35+
let pool = match pool {
36+
Some(pool) if now.saturating_sub(pool.last_reseed) <= RESEED_INTERVAL => pool,
37+
pool => {
38+
if let Some(seed) = seed_entropy() {
39+
pool.insert(Pool {
40+
rng: ChaCha20Rng::from_seed(seed),
41+
last_reseed: now,
42+
})
43+
} else {
44+
return -ENOSYS as isize;
45+
}
46+
}
47+
};
48+
49+
pool.rng.fill_bytes(buf);
50+
// Slice lengths are always <= isize::MAX so this return value cannot conflict
51+
// with error numbers.
52+
buf.len() as isize
53+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ mod arch;
7575
mod config;
7676
mod console;
7777
mod drivers;
78+
mod entropy;
7879
mod env;
7980
pub mod errno;
8081
pub(crate) mod fd;

src/macros.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,6 @@ macro_rules! dbg {
4242
};
4343
}
4444

45-
macro_rules! try_sys {
46-
($expr:expr $(,)?) => {
47-
match $expr {
48-
::core::result::Result::Ok(val) => val,
49-
::core::result::Result::Err(err) => {
50-
error!("{err}");
51-
return -1;
52-
}
53-
}
54-
};
55-
}
56-
5745
/// Runs `f` on the kernel stack.
5846
///
5947
/// All arguments and return values have to fit into registers:

src/syscalls/random.rs renamed to src/syscalls/entropy.rs

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
use core::mem::size_of;
2+
use core::slice;
3+
14
use hermit_sync::TicketMutex;
25

36
use crate::arch;
7+
use crate::entropy::{self, Flags};
8+
use crate::errno::EINVAL;
49

510
static PARK_MILLER_LEHMER_SEED: TicketMutex<u32> = TicketMutex::new(0);
611
const RAND_MAX: u64 = 2_147_483_647;
@@ -12,42 +17,74 @@ fn generate_park_miller_lehmer_random_number() -> u32 {
1217
random
1318
}
1419

15-
unsafe extern "C" fn __sys_rand32(value: *mut u32) -> i32 {
16-
let rand = try_sys!(arch::processor::generate_random_number32().ok_or("sys_rand32 failed"));
17-
unsafe {
18-
value.write(rand);
19-
}
20-
0
21-
}
20+
unsafe extern "C" fn __sys_read_entropy(buf: *mut u8, len: usize, flags: u32) -> isize {
21+
let Some(flags) = Flags::from_bits(flags) else { return -EINVAL as isize };
2222

23-
unsafe extern "C" fn __sys_rand64(value: *mut u64) -> i32 {
24-
let rand = try_sys!(arch::processor::generate_random_number64().ok_or("sys_rand64 failed"));
25-
unsafe {
26-
value.write(rand);
27-
}
28-
0
23+
let buf = unsafe {
24+
// Cap the number of bytes to be read at a time to isize::MAX to uphold
25+
// the safety guarantees of `from_raw_parts`.
26+
let len = usize::min(len, isize::MAX as usize);
27+
buf.write_bytes(0, len);
28+
slice::from_raw_parts_mut(buf, len)
29+
};
30+
31+
entropy::read(buf, flags)
2932
}
3033

31-
extern "C" fn __sys_rand() -> u32 {
32-
generate_park_miller_lehmer_random_number()
34+
/// Fill `len` bytes in `buf` with cryptographically secure random data.
35+
///
36+
/// Returns either the number of bytes written to buf (a positive value) or
37+
/// * `-EINVAL` if `flags` contains unknown flags.
38+
/// * `-ENOSYS` if the system does not support random data generation.
39+
#[no_mangle]
40+
pub unsafe extern "C" fn sys_read_entropy(buf: *mut u8, len: usize, flags: u32) -> isize {
41+
kernel_function!(__sys_read_entropy(buf, len, flags))
3342
}
3443

3544
/// Create a cryptographicly secure 32bit random number with the support of
3645
/// the underlying hardware. If the required hardware isn't available,
37-
/// the function returns `None`.
46+
/// the function returns `-1`.
3847
#[cfg(not(feature = "newlib"))]
3948
#[no_mangle]
4049
pub unsafe extern "C" fn sys_secure_rand32(value: *mut u32) -> i32 {
41-
kernel_function!(__sys_rand32(value))
50+
let mut buf = value.cast();
51+
let mut len = size_of::<u32>();
52+
while len != 0 {
53+
let res = unsafe { sys_read_entropy(buf, len, 0) };
54+
if res < 0 {
55+
return -1;
56+
}
57+
58+
buf = unsafe { buf.add(res as usize) };
59+
len -= res as usize;
60+
}
61+
62+
0
4263
}
4364

4465
/// Create a cryptographicly secure 64bit random number with the support of
4566
/// the underlying hardware. If the required hardware isn't available,
46-
/// the function returns `None`.
67+
/// the function returns -1.
4768
#[cfg(not(feature = "newlib"))]
4869
#[no_mangle]
4970
pub unsafe extern "C" fn sys_secure_rand64(value: *mut u64) -> i32 {
50-
kernel_function!(__sys_rand64(value))
71+
let mut buf = value.cast();
72+
let mut len = size_of::<u64>();
73+
while len != 0 {
74+
let res = unsafe { sys_read_entropy(buf, len, 0) };
75+
if res < 0 {
76+
return -1;
77+
}
78+
79+
buf = unsafe { buf.add(res as usize) };
80+
len -= res as usize;
81+
}
82+
83+
0
84+
}
85+
86+
extern "C" fn __sys_rand() -> u32 {
87+
generate_park_miller_lehmer_random_number()
5188
}
5289

5390
/// The function computes a sequence of pseudo-random integers
@@ -68,7 +105,7 @@ pub extern "C" fn sys_srand(seed: u32) {
68105
kernel_function!(__sys_srand(seed))
69106
}
70107

71-
pub(crate) fn random_init() {
108+
pub(crate) fn init_entropy() {
72109
let seed: u32 = arch::processor::get_timestamp() as u32;
73110

74111
*PARK_MILLER_LEHMER_SEED.lock() = seed;

src/syscalls/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use hermit_sync::InterruptTicketMutex;
55
use hermit_sync::Lazy;
66

77
pub use self::condvar::*;
8+
pub use self::entropy::*;
89
pub use self::futex::*;
910
pub use self::processor::*;
10-
pub use self::random::*;
1111
pub use self::recmutex::*;
1212
pub use self::semaphore::*;
1313
pub use self::spinlock::*;
@@ -21,6 +21,7 @@ use crate::syscalls::interfaces::SyscallInterface;
2121
use crate::{__sys_free, __sys_malloc, __sys_realloc};
2222

2323
mod condvar;
24+
mod entropy;
2425
pub(crate) mod fs;
2526
mod futex;
2627
mod interfaces;
@@ -29,7 +30,6 @@ mod lwip;
2930
#[cfg(all(feature = "tcp", not(feature = "newlib")))]
3031
mod net;
3132
mod processor;
32-
mod random;
3333
mod recmutex;
3434
mod semaphore;
3535
mod spinlock;
@@ -68,7 +68,7 @@ pub(crate) fn init() {
6868
// Perform interface-specific initialization steps.
6969
SYS.init();
7070

71-
random_init();
71+
init_entropy();
7272
#[cfg(feature = "newlib")]
7373
sbrk_init();
7474
}

0 commit comments

Comments
 (0)