Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] feat(kernel): use a sponge to generate nicer RNGs #334

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ rlibc = "1.0"
bootloader = { version = "0.10.13" }
embedded-graphics = "0.7"
mycotest = { path = "mycotest" }
merlin = { version = "3.0", default_features = false }
futures-util = { version = "0.3", default-features = false, features = ["async-await", "async-await-macro"] }
# we don't depend on this directly, but we need to ensure that `tracing` uses a
# Miri-compatible version of `once_cell`.
Expand Down
1 change: 1 addition & 0 deletions src/arch/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use hal_x86_64::{cpu, serial, vga};
pub use hal_x86_64::{cpu::entropy::seed_rng, mm, NAME};
use mycelium_util::sync::InitOnce;

pub mod entropy;
mod framebuf;
pub mod interrupt;
mod oops;
Expand Down
90 changes: 90 additions & 0 deletions src/arch/x86_64/entropy.rs
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
//! Hardware entropy sources.
use hal_x86_64::cpu::entropy;
use mycelium_util::sync::Lazy;

/// This is a wrapper type so that we don't globally implement `rand::CryptoRng`
/// for the `hal_core::cpu::entropy::PitEntropy` type, which is...Not a
/// cryptographically secure RNG...
///
/// # Notes
///
/// Use this RNG **only** for calls to `TranscriptRngBuilder::finalize`! It is
/// *not* actually cryptographically secure!
pub(crate) struct RekeyRng(entropy::PitEntropy);

pub(crate) fn fill_bytes(buf: &mut [u8]) -> usize {
static RDRAND: Lazy<Option<entropy::Rdrand>> = Lazy::new(|| entropy::Rdrand::new().ok());
// static RDTSC: Lazy<Option<hal_core::cpu::Rdtsc>> = Lazy::new(|| hal_core::cpu::Rdtsc::new().ok());
let total_len = buf.len();

if let Some(rdrand) = &*RDRAND {
fill_bytes_rdrand(rdrand, buf);
let bytes = total_len - buf.len();
tracing::trace!(bytes, "generated entropy from RDRAND");
bytes
} else {
todo!("eliza: use other entropy sources?")
}
}

fn fill_bytes_rdrand(rdrand: &entropy::Rdrand, mut buf: &mut [u8]) {
while buf.len() >= 8 {
let random = match rdrand.try_next_u64() {
Some(random) => random,
None => return,
};
let (chunk, rest) = { buf }.split_at_mut(8);
buf = rest;

chunk.copy_from_slice(&random.to_ne_bytes());
}

let remaining = buf.len();
if remaining > 4 {
let random = match rdrand.try_next_u64() {
Some(random) => random,
None => return,
};

buf.copy_from_slice(&random.to_ne_bytes()[..remaining]);
} else {
let random = match rdrand.try_next_u32() {
Some(random) => random,
None => return,
};

buf.copy_from_slice(&random.to_ne_bytes()[..remaining]);
}
}

// === impl RekeyRng ===

impl rand::RngCore for RekeyRng {
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}

fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.0.try_fill_bytes(dest)
}
}

/// This impl is necessary so that we can use this RNG for
/// `TranscriptRng::finalize`, since the `merlin` API requires that RNG be a
/// `CryptoRng`. This RNG is **not actually cryptographically secure**, but this
/// is fine, since we are not actually proving a transcript, just using the
/// transcript for its internal `#![no_std]`-compatible STROBE implementation.
impl rand::CryptoRng for RekeyRng {}

impl RekeyRng {
pub(crate) fn new() -> Self {
Self(entropy::PitEntropy::new())
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern crate alloc;
extern crate rlibc;

pub mod arch;
pub mod random;
pub mod rt;
pub mod wasm;

Expand Down Expand Up @@ -175,6 +176,8 @@ fn kernel_main() -> ! {
}
});

rt::spawn(random::entropy_upkeep(time::Duration::from_secs(1)));

let mut core = rt::Core::new();
loop {
core.run();
Expand Down
86 changes: 86 additions & 0 deletions src/random.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use crate::arch;
use maitake::{sync::RwLock, time};
use mycelium_util::sync::{spin, Lazy};

pub(crate) type SystemRng = merlin::TranscriptRng;

/// Sponge for entropy generated by hardware entropy sources.
///
/// We are not, in fact, using `merlin` to prove a public-coin argument
/// transcript, but instead, we are using its internal `#![no_std]`-compatible
/// [STROBE implementation] as a sponge for entropy generated by system hardware
/// that can be used to quickly produce a fast and secure RNG.
///
/// [STROBE implementation]: https://merlin.cool/transcript/rng.html
static SPONGE: Lazy<RwLock<merlin::Transcript>> = Lazy::new(init);

pub(crate) async fn entropy_upkeep(interval: time::Duration) {
// buffer for the entropy source output
tracing::info!("spawned RNG entropy upkeep task");
let mut buf = [0u8; 64];
loop {
// clippy tells us that this auto-deref would be done by the compiler,
// but
#[allow(clippy::explicit_auto_deref)]
fill_randomness(&mut *SPONGE.write().await, &mut buf);
time::sleep(interval).await;
}
}

/// Initialize a new random number generator.
///
/// This function spins if the RNG entropy sponge is currently being updated.
#[must_use]
pub(crate) fn new_rng_blocking() -> SystemRng {
let mut boff = spin::Backoff::new();
let sponge = loop {
if let Some(sponge) = SPONGE.try_read() {
break sponge;
}
boff.spin();
};
build_rng(&sponge)
}

/// Initialize a new random number generator.
///
/// This function waits until the RNG entropy sponge is done being updated.
#[allow(dead_code)] // i'll use this later
pub(crate) async fn new_rng() -> SystemRng {
let sponge = SPONGE.read().await;
build_rng(&sponge)
}

fn build_rng(sponge: &merlin::Transcript) -> SystemRng {
tracing::debug!("building a new RNG");
sponge
.build_rng()
.finalize(&mut arch::entropy::RekeyRng::new())
}

fn init() -> RwLock<merlin::Transcript> {
tracing::info!("initializing system RNG...");
let mut sponge = merlin::Transcript::new(b"rng");
let mut buf = [0u8; 64];
fill_randomness(&mut sponge, &mut buf);

tracing::info!("RNG initialized!");
RwLock::new(sponge)
}

fn fill_randomness(sponge: &mut merlin::Transcript, buf: &mut [u8]) {
// Number of iterations of filling the sponge to do if we haven't run out of
// rdrand randomness.
//
// This is just so that the bg task will never loop infinitely if rdrand is
// refilling too fast.
const MAX_ITERS: usize = 64;
for _ in 0..MAX_ITERS {
let bytes = arch::entropy::fill_bytes(buf);
sponge.append_message(b"entropy", &buf[..bytes]);
if bytes < buf.len() {
// called rdrand too much, i guess!
break;
}
}
}
9 changes: 2 additions & 7 deletions src/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ pub struct Core {
/// have a situation where all idle steal from the first available worker,
/// resulting in other cores ending up with huge queues of idle tasks while
/// the first core's queue is always empty.
///
/// This is *not* a cryptographically secure random number generator, since
/// randomness of this value is not required for security. Instead, it just
/// helps ensure a good distribution of load. Therefore, we use a fast,
/// non-cryptographic RNG.
rng: rand_xoshiro::Xoroshiro128PlusPlus,
rng: crate::random::SystemRng,
}

struct Runtime {
Expand Down Expand Up @@ -82,7 +77,7 @@ impl Core {
Self {
scheduler,
id,
rng: crate::arch::seed_rng(),
rng: crate::random::new_rng_blocking(),
running: AtomicBool::new(false),
}
}
Expand Down