base: fix round-8 deferred stubs in initfs, randd, ptyd
initfs/tools: buffer inode table writes in memory and flush with a single write_all_at instead of one syscall per inode header. randd: build a proper entropy pool using SHA-256 that mixes hardware RNG (RDRAND/RNDRRS) with timing-derived entropy. The pool is re-seeded every 4096 reads and on every write. User-supplied entropy from writes is added to the pool rather than directly replacing the PRNG state. ptyd: VLNEXT now properly quotes the next input byte — sets a flag that causes the next byte to bypass all control-character processing and be pushed directly to the line buffer. VDISCARD now flushes the cooked input buffer instead of being a silent no-op.
This commit is contained in:
+21
-13
@@ -66,6 +66,7 @@ struct State<'path> {
|
||||
buffer: Box<[u8]>,
|
||||
inode_table_offset: u32,
|
||||
page_size: u16,
|
||||
inode_table: Vec<u8>,
|
||||
}
|
||||
|
||||
fn write_all_at(file: &File, buf: &[u8], offset: u64, r#where: &str) -> Result<()> {
|
||||
@@ -263,11 +264,8 @@ fn write_inode(
|
||||
write_result: WriteResult,
|
||||
inode: u16,
|
||||
) -> Result<()> {
|
||||
let inode_size: u32 = std::mem::size_of::<initfs::InodeHeader>()
|
||||
.try_into()
|
||||
.expect("inode header length cannot fit within u32");
|
||||
let inode_size = std::mem::size_of::<initfs::InodeHeader>();
|
||||
|
||||
// TODO: Use main buffer and write in bulk.
|
||||
let mut inode_buf = [0_u8; std::mem::size_of::<initfs::InodeHeader>()];
|
||||
|
||||
let inode_hdr = plain::from_mut_bytes::<initfs::InodeHeader>(&mut inode_buf)
|
||||
@@ -280,17 +278,15 @@ fn write_inode(
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"Writing inode index {} from offset {}",
|
||||
"Staging inode index {} from offset {}",
|
||||
inode,
|
||||
state.inode_table_offset
|
||||
);
|
||||
write_all_at(
|
||||
&state.file,
|
||||
&inode_buf,
|
||||
u64::from(state.inode_table_offset + u32::from(inode) * inode_size),
|
||||
"write_inode",
|
||||
)
|
||||
.context("failed to write inode struct to disk image")
|
||||
|
||||
let byte_offset = usize::from(inode) * inode_size;
|
||||
state.inode_table[byte_offset..byte_offset + inode_size].copy_from_slice(&inode_buf);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn allocate_and_write_dir(
|
||||
state: &mut State,
|
||||
@@ -406,7 +402,15 @@ fn allocate_contents_and_write_inodes(state: &mut State, dir: &Dir) -> Result<()
|
||||
let write_result = allocate_and_write_dir(state, dir, &mut current_inode)
|
||||
.context("failed to allocate and write all directories and files")?;
|
||||
|
||||
write_inode(state, initfs::InodeType::Dir, write_result, start_inode)
|
||||
write_inode(state, initfs::InodeType::Dir, write_result, start_inode)?;
|
||||
|
||||
write_all_at(
|
||||
&state.file,
|
||||
&state.inode_table,
|
||||
u64::from(state.inode_table_offset),
|
||||
"flush inode table",
|
||||
)
|
||||
.context("failed to write inode table to disk image")
|
||||
}
|
||||
|
||||
struct OutputImageGuard<'a> {
|
||||
@@ -491,6 +495,7 @@ pub fn archive(
|
||||
buffer: vec![0_u8; BUFFER_SIZE].into_boxed_slice(),
|
||||
inode_table_offset: 0,
|
||||
page_size: detect_page_size(),
|
||||
inode_table: Vec::new(),
|
||||
};
|
||||
|
||||
let root_path = source;
|
||||
@@ -546,6 +551,9 @@ pub fn archive(
|
||||
|
||||
state.inode_table_offset = inode_table_offset.0.get();
|
||||
|
||||
let inode_entry_size = std::mem::size_of::<initfs::InodeHeader>();
|
||||
state.inode_table = vec![0_u8; inode_entry_size * usize::from(state.inode_count)];
|
||||
|
||||
allocate_contents_and_write_inodes(&mut state, &root)?;
|
||||
|
||||
let current_system_time = std::time::SystemTime::now();
|
||||
|
||||
+19
-6
@@ -14,6 +14,7 @@ pub struct Pty {
|
||||
pub mosi: VecDeque<Vec<u8>>,
|
||||
pub timeout_count: u64,
|
||||
pub timeout_character: Option<u64>,
|
||||
pub lnext: bool,
|
||||
}
|
||||
|
||||
impl Pty {
|
||||
@@ -28,6 +29,7 @@ impl Pty {
|
||||
mosi: VecDeque::new(),
|
||||
timeout_count: 0,
|
||||
timeout_character: None,
|
||||
lnext: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +67,18 @@ impl Pty {
|
||||
for &byte in buf.iter() {
|
||||
let mut b = byte;
|
||||
|
||||
if self.lnext {
|
||||
self.lnext = false;
|
||||
if b != 0 {
|
||||
if echo {
|
||||
self.output(&[b]);
|
||||
}
|
||||
self.timeout_character = Some(self.timeout_count);
|
||||
self.cooked.push(b);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Input translation
|
||||
if b == b'\n' {
|
||||
if inlcr {
|
||||
@@ -220,16 +234,15 @@ impl Pty {
|
||||
}
|
||||
|
||||
if is_cc(b, VLNEXT) && iexten {
|
||||
// VLNEXT quotes the next input byte so control characters
|
||||
// can be entered literally. Real implementation requires
|
||||
// reading the next byte outside of the current for loop;
|
||||
// for now it is acknowledged but does not consume the
|
||||
// following byte. Cross-referenced with POSIX termios(3).
|
||||
self.lnext = true;
|
||||
if echoe {
|
||||
self.output(&[b]);
|
||||
}
|
||||
b = 0;
|
||||
}
|
||||
|
||||
if is_cc(b, VDISCARD) && iexten {
|
||||
// Toggle output flushing; not fully implemented in this PTY.
|
||||
self.cooked.clear();
|
||||
b = 0;
|
||||
}
|
||||
|
||||
|
||||
+76
-54
@@ -1,7 +1,8 @@
|
||||
use std::arch::asm;
|
||||
use std::time::Instant;
|
||||
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use rand_core::RngCore;
|
||||
use rand_core::{RngCore, SeedableRng};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use raw_cpuid::CpuId;
|
||||
@@ -13,8 +14,6 @@ use syscall::flag::{EventFlags, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_WRONLY};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error, Result, EACCES, EBADF, EEXIST, ENOENT, EPERM, MODE_CHR};
|
||||
|
||||
// Create an RNG Seed to create initial seed from the rdrand intel instruction
|
||||
use rand_core::SeedableRng;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
// This Daemon implements a Cryptographically Secure Random Number Generator
|
||||
@@ -24,27 +23,54 @@ use sha2::{Digest, Sha256};
|
||||
|
||||
// Rand crate recommends at least 256 bits of entropy to seed the RNG
|
||||
const SEED_BYTES: usize = 32;
|
||||
const RESEED_INTERVAL: u64 = 4096;
|
||||
|
||||
fn collect_timing_entropy() -> [u8; SEED_BYTES] {
|
||||
let mut buf = [0u8; SEED_BYTES];
|
||||
let start = Instant::now();
|
||||
for slot in buf.chunks_mut(8) {
|
||||
let t1 = Instant::now();
|
||||
let d1 = t1.duration_since(start);
|
||||
let t2 = Instant::now();
|
||||
let d2 = t2.duration_since(t1);
|
||||
let mixed = d1.as_nanos().wrapping_mul(0x517cc1b727220a95)
|
||||
^ d2.as_nanos().wrapping_add(0x6c62272e07bb0142);
|
||||
let bytes = mixed.to_le_bytes();
|
||||
let copy_len = slot.len().min(bytes.len());
|
||||
slot[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
fn mix_seed(sources: &[&[u8]]) -> [u8; SEED_BYTES] {
|
||||
let mut hasher = Sha256::new();
|
||||
for src in sources {
|
||||
hasher.input(src);
|
||||
}
|
||||
let hash = hasher.result();
|
||||
let mut seed = [0u8; SEED_BYTES];
|
||||
seed.copy_from_slice(hash.as_slice());
|
||||
seed
|
||||
}
|
||||
|
||||
/// Create a true random seed for the RNG if hardware support is present.
|
||||
/// On Intel x64 from rdrand instruction.
|
||||
/// On AArch64 from RNDRRS system register.
|
||||
/// Will seed with a zero (insecure) if getting support is not present.
|
||||
/// Mixes hardware randomness with timing-derived entropy.
|
||||
fn create_rdrand_seed() -> [u8; SEED_BYTES] {
|
||||
let mut rng = [0; SEED_BYTES];
|
||||
let mut have_seeded = false;
|
||||
let mut hw_seed = [0u8; SEED_BYTES];
|
||||
let mut have_hw = false;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
if CpuId::new().get_feature_info().unwrap().has_rdrand() {
|
||||
for i in 0..SEED_BYTES / 8 {
|
||||
// We get 8 bytes at a time from rdrand instruction
|
||||
let rand: u64;
|
||||
unsafe {
|
||||
asm!("rdrand rax", out("rax") rand);
|
||||
}
|
||||
|
||||
rng[i * 8..(i * 8 + 8)].copy_from_slice(&rand.to_le_bytes());
|
||||
hw_seed[i * 8..(i * 8 + 8)].copy_from_slice(&rand.to_le_bytes());
|
||||
}
|
||||
have_seeded = true;
|
||||
have_hw = true;
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -62,21 +88,24 @@ fn create_rdrand_seed() -> [u8; SEED_BYTES] {
|
||||
if is_cpu_feature_detected("rand") {
|
||||
let mut failure = false;
|
||||
for i in 0..SEED_BYTES / 8 {
|
||||
// We get 8 bytes at a time from RNDRRS register
|
||||
let rand: u64;
|
||||
unsafe {
|
||||
asm!("mrs {}, s3_3_c2_c4_1", out(reg) rand); // rndrrs
|
||||
asm!("mrs {}, s3_3_c2_c4_1", out(reg) rand);
|
||||
}
|
||||
failure |= rand == 0;
|
||||
rng[i * 8..(i * 8 + 8)].copy_from_slice(&rand.to_le_bytes());
|
||||
hw_seed[i * 8..(i * 8 + 8)].copy_from_slice(&rand.to_le_bytes());
|
||||
}
|
||||
have_seeded = !failure;
|
||||
have_hw = !failure;
|
||||
}
|
||||
} // TODO integrate alternative entropy sources
|
||||
if !have_seeded {
|
||||
println!("randd: Seeding failed, no entropy source. Random numbers on this platform are NOT SECURE");
|
||||
}
|
||||
rng
|
||||
let timing = collect_timing_entropy();
|
||||
let seed = if have_hw {
|
||||
mix_seed(&[&hw_seed, &timing])
|
||||
} else {
|
||||
println!("randd: No hardware RNG. Seeding from timing entropy only. Random numbers on this platform are NOT SECURE");
|
||||
mix_seed(&[&timing])
|
||||
};
|
||||
seed
|
||||
}
|
||||
|
||||
/// Contains information about an open file
|
||||
@@ -104,21 +133,24 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to represent the rand scheme.
|
||||
struct RandScheme {
|
||||
prng: ChaCha20Rng,
|
||||
// ChaCha20 is a Cryptographically Secure PRNG
|
||||
// https://docs.rs/rand/0.5.0/rand/prng/chacha/struct.ChaChaRng.html
|
||||
// Allows 2^64 streams of random numbers, which we will equate with file numbers
|
||||
handles: HandleMap<Handle>,
|
||||
pool: Sha256,
|
||||
read_since_reseed: u64,
|
||||
}
|
||||
|
||||
impl RandScheme {
|
||||
/// Create new rand scheme from a message socket
|
||||
fn new() -> RandScheme {
|
||||
let seed = create_rdrand_seed();
|
||||
let mut pool = Sha256::new();
|
||||
pool.input(&seed);
|
||||
pool.input(&collect_timing_entropy());
|
||||
RandScheme {
|
||||
prng: ChaCha20Rng::from_seed(create_rdrand_seed()),
|
||||
prng: ChaCha20Rng::from_seed(seed),
|
||||
handles: HandleMap::new(),
|
||||
pool,
|
||||
read_since_reseed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,18 +169,18 @@ impl RandScheme {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Reseed the CSPRNG with the supplied entropy.
|
||||
/// TODO add this to an entropy pool and give a limited estimate to the amount of entropy
|
||||
/// TODO consider having trusted and untrusted entropy URIs, with different permissions.
|
||||
fn reseed_prng(&mut self, entropy: &[u8]) {
|
||||
// Need to fill a fixed size array for the from_seed, so we'll do 256 bit
|
||||
// array and has the entropy into it.
|
||||
let mut digest = Sha256::new();
|
||||
digest.input(entropy);
|
||||
let hash = digest.result();
|
||||
let mut entropy_array: [u8; SEED_BYTES] = [0; SEED_BYTES];
|
||||
entropy_array.copy_from_slice(hash.as_slice());
|
||||
self.prng = ChaCha20Rng::from_seed(entropy_array);
|
||||
fn reseed_from_pool(&mut self) {
|
||||
self.pool.input(&collect_timing_entropy());
|
||||
|
||||
let mut prng_out = [0u8; SEED_BYTES];
|
||||
self.prng.fill_bytes(&mut prng_out);
|
||||
self.pool.input(&prng_out);
|
||||
|
||||
let hash = self.pool.result_reset();
|
||||
let mut seed = [0u8; SEED_BYTES];
|
||||
seed.copy_from_slice(hash.as_slice());
|
||||
self.prng = ChaCha20Rng::from_seed(seed);
|
||||
self.read_since_reseed = 0;
|
||||
}
|
||||
|
||||
fn open_inner(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result<OpenResult> {
|
||||
@@ -208,14 +240,16 @@ impl SchemeSync for RandScheme {
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
// Check fd and permissions
|
||||
self.can_perform_op_on_fd(id, O_RDONLY)?;
|
||||
|
||||
// Setting the stream will ensure that if two clients are reading concurrently, they won't get the same numbers
|
||||
self.prng.set_stream(id as u64); // Should probably find a way to re-instate the counter for this stream, but
|
||||
// not doing so won't make the output any less 'random'
|
||||
self.prng.set_stream(id as u64);
|
||||
self.prng.fill_bytes(buf);
|
||||
|
||||
self.read_since_reseed += 1;
|
||||
if self.read_since_reseed >= RESEED_INTERVAL {
|
||||
self.reseed_from_pool();
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
@@ -227,22 +261,10 @@ impl SchemeSync for RandScheme {
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
// Check fd and permissions
|
||||
self.can_perform_op_on_fd(id, O_WRONLY)?;
|
||||
|
||||
// TODO - when we support other entropy sources, just add this to an entropy pool
|
||||
// TODO - consider having trusted and untrusted entropy writing paths
|
||||
// We have a healthy mistrust of the entropy we're being given, so we won't seed just with
|
||||
// that as the resulting numbers would be predictable based on this input
|
||||
// we'll take 512 bits (arbitrary) from the current PRNG, and seed with that
|
||||
// and the supplied data.
|
||||
|
||||
let mut rng_buf: [u8; SEED_BYTES] = [0; SEED_BYTES];
|
||||
self.prng.fill_bytes(&mut rng_buf);
|
||||
let mut rng_vec = Vec::new();
|
||||
rng_vec.extend(&rng_buf);
|
||||
rng_vec.extend(buf);
|
||||
self.reseed_prng(&rng_vec);
|
||||
self.pool.input(buf);
|
||||
self.reseed_from_pool();
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user