diff --git a/initfs/tools/src/lib.rs b/initfs/tools/src/lib.rs index c026220174..1ee20f56c2 100644 --- a/initfs/tools/src/lib.rs +++ b/initfs/tools/src/lib.rs @@ -66,6 +66,7 @@ struct State<'path> { buffer: Box<[u8]>, inode_table_offset: u32, page_size: u16, + inode_table: Vec, } 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::() - .try_into() - .expect("inode header length cannot fit within u32"); + let inode_size = std::mem::size_of::(); - // TODO: Use main buffer and write in bulk. let mut inode_buf = [0_u8; std::mem::size_of::()]; let inode_hdr = plain::from_mut_bytes::(&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::(); + 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(); diff --git a/ptyd/src/pty.rs b/ptyd/src/pty.rs index 80cd2957a4..338e6157c6 100644 --- a/ptyd/src/pty.rs +++ b/ptyd/src/pty.rs @@ -14,6 +14,7 @@ pub struct Pty { pub mosi: VecDeque>, pub timeout_count: u64, pub timeout_character: Option, + 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; } diff --git a/randd/src/main.rs b/randd/src/main.rs index 9d72417d71..052488df24 100644 --- a/randd/src/main.rs +++ b/randd/src/main.rs @@ -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, + 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 { @@ -208,14 +240,16 @@ impl SchemeSync for RandScheme { _fcntl_flags: u32, _ctx: &CallerCtx, ) -> Result { - // 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 { - // 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()) }