From 0eb534417e77fa57db2ca004a8358bed80780a17 Mon Sep 17 00:00:00 2001 From: Kalfar Date: Tue, 7 Jan 2020 22:00:08 +0000 Subject: [PATCH] Change PRNG to be ChaCha20 PRNG and seeding with 512 bits of entropy from rdrand (if available) Add permissions to scheme operations Add ability to change owner/group/mode Add ability to write entropy to the PRNG scheme Add streaming of ChaCha20 PRNG output based on file open operation. Add not seeded message if rdrand not present on CPU Add extra TODOs for new future work --- Cargo.lock | 141 ++++++++++------- Cargo.toml | 4 +- src/main.rs | 429 ++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 453 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25245035e7..4c660ebb37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,56 +6,96 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand" -version = "0.3.23" +name = "block-buffer" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "rand" -version = "0.4.6" +name = "block-padding" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "rand_core" +name = "byte-tools" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "randd" version = "0.1.0" dependencies = [ - "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "raw-cpuid 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -66,49 +106,42 @@ dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "winapi" -version = "0.3.8" +name = "sha2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "typenum" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" -"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum raw-cpuid 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13b844e4049605ff38fed943f5c7b2c691fad68d9d5bf074d2720554c4e48246" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" diff --git a/Cargo.toml b/Cargo.toml index 95c8a04049..c29ebdca85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,10 @@ name = "randd" version = "0.1.0" [dependencies] -rand = "0.3" +rand_chacha = "0.2" +rand_core = "0.5" redox_syscall = "0.1" +sha2 = "0.8" [target.'cfg(target_arch = "x86_64")'.dependencies] raw-cpuid = "2.0" diff --git a/src/main.rs b/src/main.rs index 508326d484..1a40e6bc38 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,56 +2,369 @@ extern crate syscall; +extern crate rand_chacha; +extern crate rand_core; #[cfg(target_arch = "x86_64")] extern crate raw_cpuid; -extern crate rand; +extern crate sha2; use std::fs::File; use std::io::{Read, Write}; -use std::os::unix::io::AsRawFd; -use rand::chacha::ChaChaRng; -use rand::Rng; +use rand_chacha::ChaCha20Rng; +use rand_core::RngCore; + +pub const MODE_PERM: u16 = 0x0FFF; +pub const MODE_EXEC: u16 = 0o1; +pub const MODE_WRITE: u16 = 0o2; +pub const MODE_READ: u16 = 0o4; #[cfg(target_arch = "x86_64")] use raw_cpuid::CpuId; -use syscall::{Error, Result, SchemeMut, EINVAL, MODE_CHR}; use syscall::data::{Packet, Stat}; use syscall::flag::EVENT_READ; +use syscall::{ + Error, Result, SchemeMut, EBADF, EBADFD, EEXIST, EINVAL, ENOENT, EPERM, MODE_CHR, O_CLOEXEC, + O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_STAT, O_WRONLY, +}; -//TODO: Use a CSPRNG, allow write of entropy +// Create an RNG Seed to create initial seed from the rdrand intel instruction +use rand_core::SeedableRng; +use sha2::{Digest, Sha256}; +use std::collections::BTreeMap; +use std::num::Wrapping; + +// TODO consider adding blocking read based on available entropy as per linux /dev/random +// (compared to /dev/urandom which we are equivalent to) + +// Default file access mode for PRNG +const DEFAULT_PRNG_MODE: u16 = 0o644; +// Rand crate recommends at least 256 bits of entropy to seed the RNG +const SEED_BYTES: usize = 32; + +/// Create a true random seed for the RNG from the Intel x64 rdrand instruction if present. +/// Will seed with a zero (insecure) if rdrand not present. +fn create_rdrand_seed() -> [u8; SEED_BYTES] { + let mut rng = [0; SEED_BYTES]; + let mut have_seeded = 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" + : "={rax}"(rand) + : + : + : "intel", "volatile"); + } + + rng[i * 8..(i * 8 + 8)].copy_from_slice(&rand.to_le_bytes()); + } + have_seeded = true; + } + } // TODO integrate alternative entropy sources + if !have_seeded { + println!("randd: Seeding failed, no entropy source. Random numbers on this platform are NOT SECURE"); + } + rng +} + +/// Contains information about an open file +struct OpenFileInfo { + o_flags: usize, + /// Flags used when opening file. + uid: u32, + gid: u32, + file_stat: Stat, +} + +impl OpenFileInfo { + /// Tests if the current user has enough permissions to view the file, op is the operation, + /// like read and write, these modes are MODE_EXEC, MODE_READ, and MODE_WRITE + /// Copied from redoxfs + fn permission(&self, op: u16) -> bool { + let mut perm = self.file_stat.st_mode & 0o7; + if self.uid == self.file_stat.st_uid { + // If self.mode is 101100110, >> 6 would be 000000101 + // 0o7 is octal for 111, or, when expanded to 9 digits is 000000111 + perm |= (self.file_stat.st_mode >> 6) & 0o7; + // Since we erased the GID and OTHER bits when >>6'ing, |= will keep those bits in place. + } + if self.gid == self.file_stat.st_gid || self.file_stat.st_gid == 0 { + perm |= (self.file_stat.st_mode >> 3) & 0o7; + } + if self.uid == 0 { + //set the `other` bits to 111 + perm |= 0o7; + } + perm & op == op + } + fn o_flag_set(&self, f: usize) -> bool { + return (f & self.o_flags) == f; + } +} + +/// Struct to represent the rand scheme. struct RandScheme { socket: File, - prng: ChaChaRng + 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 + prng_stat: Stat, + open_descriptors: BTreeMap, // Cannot use HashMap as the implementation + // calls the system RNG (us) for entropy to protect against HashDOS attacks. + // Trying to create a HashMap causes a system crash. + // + next_fd: Wrapping, +} + +impl RandScheme { + /// Create new rand scheme from a message socket + fn new(socket: File) -> RandScheme { + RandScheme { + socket, + prng: ChaCha20Rng::from_seed(create_rdrand_seed()), + prng_stat: Stat { + st_mode: MODE_CHR | DEFAULT_PRNG_MODE, + st_gid: 0, + st_uid: 0, + ..Default::default() + }, + open_descriptors: BTreeMap::new(), + next_fd: Wrapping(0), + } + } + + /// Gets the open file info for a file descriptor if it is open - error otherwise. + fn get_fd(&self, fd: usize) -> Result<&OpenFileInfo> { + // Check we've got a valid file descriptor + let file_info = match self.open_descriptors.get(&fd) { + Some(m) => m, + None => return Err(Error::new(EBADF)), + }; + Ok(file_info) + } + /// Checks to see if the op (MODE_READ, MODE_WRITE) can be performed on the open file + /// descriptor - Will return the open file info if successful, and error if the file + /// descriptor is invalid, or the permission is denied. + fn can_perform_op_on_fd(&self, fd: usize, op: u16) -> Result<&OpenFileInfo> { + let file_info = self.get_fd(fd)?; + if !file_info.permission(op) { + return Err(Error::new(EPERM)); + } + Ok(file_info) + } + /// 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); + } +} + +#[test] +fn test_scheme_perms() { + let mut scheme = RandScheme::new(File::open(".").unwrap()); + scheme.prng_stat.st_mode = MODE_CHR | 0o200; + scheme.prng_stat.st_uid = 1; + scheme.prng_stat.st_gid = 1; + assert!(scheme.open("/".as_bytes(), O_RDWR, 1, 1).is_err()); + assert!(scheme.open("/".as_bytes(), O_RDONLY, 1, 1).is_err()); + + scheme.prng_stat.st_mode = MODE_CHR | 0o400; + let mut fd = scheme.open("".as_bytes(), O_RDONLY, 1, 1).unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_ok()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_err()); + assert!(scheme.close(fd).is_ok()); + + assert!(scheme.open("".as_bytes(), O_WRONLY, 1, 1).is_err()); + assert!(scheme.open("".as_bytes(), O_RDWR, 1, 1).is_err()); + + scheme.prng_stat.st_mode = MODE_CHR | 0o600; + fd = scheme.open("".as_bytes(), O_RDWR, 1, 1).unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_ok()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_ok()); + assert!(scheme.close(fd).is_ok()); + + fd = scheme.open("".as_bytes(), O_STAT, 2, 2).unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_err()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_err()); + assert!(scheme.close(fd).is_ok()); + fd = scheme + .open("".as_bytes(), O_STAT | O_CLOEXEC, 2, 2) + .unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_err()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_err()); + assert!(scheme.close(fd).is_ok()); + + // Try another user in group (no group perms) + fd = scheme + .open("".as_bytes(), O_STAT | O_CLOEXEC, 2, 1) + .unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_err()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_err()); + assert!(scheme.close(fd).is_ok()); + scheme.prng_stat.st_mode = MODE_CHR | 0o660; + fd = scheme + .open("".as_bytes(), O_STAT | O_CLOEXEC, 2, 1) + .unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_ok()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_ok()); + assert!(scheme.close(fd).is_ok()); + + // Check root can do anything + scheme.prng_stat.st_mode = MODE_CHR | 0o000; + fd = scheme + .open("".as_bytes(), O_STAT | O_CLOEXEC, 0, 0) + .unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_ok()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_ok()); + assert!(scheme.close(fd).is_ok()); + + // Check the rand:/urandom URL (Equivalent to rand:/) + scheme.prng_stat.st_mode = MODE_CHR | 0o660; + fd = scheme + .open("/urandom".as_bytes(), O_STAT | O_CLOEXEC, 2, 1) + .unwrap(); + assert!(scheme.can_perform_op_on_fd(fd, MODE_READ).is_ok()); + assert!(scheme.can_perform_op_on_fd(fd, MODE_WRITE).is_ok()); + assert!(scheme.close(fd).is_ok()); } impl SchemeMut for RandScheme { - fn open(&mut self, _path: &[u8], _flags: usize, _uid: u32, _gid: u32) -> Result { - Ok(0) + fn open(&mut self, path: &[u8], flags: usize, uid: u32, gid: u32) -> Result { + // We are only allowing + // reads/writes from rand:/ and rand:/urandom - the root directory on its own is passed as an empty slice + if path != "".as_bytes() && path != "/urandom".as_bytes() { + return Err(Error::new(ENOENT)); + } + if flags & (O_CREAT | O_EXCL) == O_CREAT | O_EXCL { + return Err(Error::new(EEXIST)); + } + + let fd = self.next_fd; + let open_file_info = OpenFileInfo { + o_flags: flags, + file_stat: self.prng_stat, + uid, + gid, + }; + + if (open_file_info.o_flag_set(O_RDONLY) || open_file_info.o_flag_set(O_RDWR)) + && !open_file_info.permission(MODE_READ) + { + return Err(Error::new(EPERM)); + } + if (open_file_info.o_flag_set(O_WRONLY) || open_file_info.o_flag_set(O_RDWR)) + && !open_file_info.permission(MODE_WRITE) + { + return Err(Error::new(EPERM)); + } + self.open_descriptors.insert(fd.0, open_file_info); + // Get the next file descriptor + self.next_fd += Wrapping(1); + // If we've looped round there's a small chance that the file descriptor still exists, so loop till we get one that doesn't + loop { + if !self.open_descriptors.contains_key(&self.next_fd.0) { + break; + } else { + self.next_fd += Wrapping(1); + } + } + Ok(fd.0) + } + + fn chmod(&mut self, path: &[u8], mode: u16, uid: u32, gid: u32) -> Result { + // Defer to fchmod + let fd = self.open(path, O_WRONLY, uid, gid)?; + self.fchmod(fd, mode) } fn dup(&mut self, file: usize, buf: &[u8]) -> Result { - if ! buf.is_empty() { + if !buf.is_empty() { return Err(Error::new(EINVAL)); } Ok(file) } + /* Resource operations */ + fn read(&mut self, file: usize, buf: &mut [u8]) -> Result { + // Check fd and permissions + self.can_perform_op_on_fd(file, MODE_READ)?; - fn read(&mut self, _file: usize, buf: &mut [u8]) -> Result { - let mut i = 0; - for chunk in buf.chunks_mut(8) { - let mut rand = self.prng.next_u64(); - for b in chunk.iter_mut() { - *b = rand as u8; - rand = rand >> 8; - i += 1; - } - } - Ok(i) + // Setting the stream will ensure that if two clients are reading concurrently, they won't get the same numbers + self.prng.set_stream(file 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.fill_bytes(buf); + + Ok(buf.len()) } + fn write(&mut self, file: usize, buf: &[u8]) -> Result { + // Check fd and permissions + self.can_perform_op_on_fd(file, MODE_WRITE)?; + + // 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); + Ok(buf.len()) + } + + fn fchmod(&mut self, file: usize, mode: u16) -> Result { + // Check fd and permissions + let file_info = self.get_fd(file)?; + // only root and owner can chmod + if file_info.uid != file_info.file_stat.st_uid && file_info.uid != 0 { + return Err(Error::new(EPERM)); + } + + self.prng_stat.st_mode = MODE_CHR | mode; + Ok(0) + } + + fn fchown(&mut self, file: usize, uid: u32, gid: u32) -> Result { + // Check fd and permissions + let file_info = self.get_fd(file)?; + // only root and owner can chmod + if file_info.uid != file_info.file_stat.st_uid && file_info.uid != 0 { + return Err(Error::new(EPERM)); + } + + self.prng_stat.st_uid = uid; + self.prng_stat.st_gid = gid; + Ok(0) + } + + fn fcntl(&mut self, _id: usize, _cmd: usize, _arg: usize) -> Result { + // Just ignore this. + Ok(0) + } + + fn fevent(&mut self, _id: usize, _flags: usize) -> Result { + Ok(EVENT_READ) + } fn fpath(&mut self, _file: usize, buf: &mut [u8]) -> Result { let mut i = 0; let scheme_path = b"rand"; @@ -62,68 +375,52 @@ impl SchemeMut for RandScheme { Ok(i) } - fn fstat(&mut self, _id: usize, stat: &mut Stat) -> Result { - *stat = Stat { - st_mode: MODE_CHR | 0o666, - ..Default::default() - }; + fn fstat(&mut self, file: usize, stat: &mut Stat) -> Result { + // Check fd and permissions + self.can_perform_op_on_fd(file, MODE_READ)?; + + *stat = self.prng_stat.clone(); Ok(0) } - fn fevent(&mut self, _id: usize, _flags: usize) -> Result { - Ok(EVENT_READ) - } - - fn fcntl(&mut self, _id: usize, _cmd: usize, _arg: usize) -> Result { - Ok(0) - } - - fn close(&mut self, _file: usize) -> Result { - Ok(0) + fn close(&mut self, file: usize) -> Result { + // just remove the file descriptor from the open descriptors + match self.open_descriptors.remove(&file) { + Some(_) => Ok(0), + None => Err(Error::new(EBADFD)), + } } } -fn main(){ - let mut has_rdrand = false; - - #[cfg(target_arch = "x86_64")] - { - has_rdrand = CpuId::new().get_feature_info().unwrap().has_rdrand(); - } - +fn main() { // Daemonize if unsafe { syscall::clone(0).unwrap() } == 0 { let socket = File::create(":rand").expect("rand: failed to create rand scheme"); - let mut rng = ChaChaRng::new_unseeded(); - - if has_rdrand { - println!("rand: seeding with rdrand"); - let rand: u64; - unsafe { - asm!("rdrand rax" - : "={rax}"(rand) - : - : - : "intel", "volatile"); - } - rng.set_counter(0, rand); - } else { - println!("rand: unseeded"); - } - - let mut scheme = RandScheme{socket, prng: rng}; + let mut scheme = RandScheme::new(socket); syscall::setrens(0, 0).expect("randd: failed to enter null namespace"); loop { let mut packet = Packet::default(); - if scheme.socket.read(&mut packet).expect("rand: failed to read events from rand scheme") == 0 { - break; + match scheme.socket.read(&mut packet) { + Ok(s) => { + if s == 0 { + break; + } + scheme.handle(&mut packet); + match scheme.socket.write(&packet) { + Err(e) => println!("Error writing packet {}", e), + _ => {} + } + } + Err(e) => println!("Error reading packet {}", e), } - scheme.handle(&mut packet); - scheme.socket.write(&packet).expect("rand: failed to write responses to rand scheme"); + scheme + .socket + .write(&packet) + .expect("rand: failed to write responses to rand scheme"); } } }