From 7e8eda88f7d965dabffafee1f03d13b01027cc65 Mon Sep 17 00:00:00 2001 From: "Lance R. Vick" Date: Sun, 23 Oct 2022 15:15:23 -0700 Subject: [PATCH] Refactor init into modules, and implement simple/working entropy seeding --- src/aws/Cargo.toml | 12 ++ src/aws/src/lib.rs | 65 ++++++++++ src/init/Cargo.lock | 21 +++- src/init/Cargo.toml | 4 +- src/init/init.rs | 268 +++++------------------------------------- src/system/Cargo.toml | 9 ++ src/system/src/lib.rs | 169 ++++++++++++++++++++++++++ 7 files changed, 306 insertions(+), 242 deletions(-) create mode 100644 src/aws/Cargo.toml create mode 100644 src/aws/src/lib.rs create mode 100644 src/system/Cargo.toml create mode 100644 src/system/src/lib.rs diff --git a/src/aws/Cargo.toml b/src/aws/Cargo.toml new file mode 100644 index 0000000..27a9f06 --- /dev/null +++ b/src/aws/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "aws" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2.134" +nsm_lib = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="nsm-lib", optional = false } +nsm_api = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="aws-nitro-enclaves-nsm-api", optional = false } +system = { path = "../system"} diff --git a/src/aws/src/lib.rs b/src/aws/src/lib.rs new file mode 100644 index 0000000..14acfe2 --- /dev/null +++ b/src/aws/src/lib.rs @@ -0,0 +1,65 @@ +use system::{dmesg, SystemError}; + +// Signal to Nitro hypervisor that booting was successful +fn nitro_heartbeat() { + use system::socket_connect; + use libc::{write, read, close, AF_VSOCK}; + let mut buf: [u8; 1] = [0; 1]; + buf[0] = 0xB7; // AWS Nitro heartbeat value + let fd = match socket_connect(AF_VSOCK, 9000, 3) { + Ok(f)=> f, + Err(e)=> { + eprintln!("{}", e); + return + }, + }; + unsafe { + write(fd, buf.as_ptr() as _, 1); + read(fd, buf.as_ptr() as _, 1); + close(fd); + } + dmesg(format!("Sent NSM heartbeat")); +} + +// Get entropy sample from Nitro device +pub fn get_entropy(size: usize) -> Result, SystemError> { + use nsm_api::api::ErrorCode; + use nsm_lib::{nsm_get_random, nsm_lib_init}; + let nsm_fd = nsm_lib_init(); + if nsm_fd < 0 { + return Err(SystemError { + message: String::from("Failed to connect to NSM device") + }); + }; + let mut dest = Vec::with_capacity(size); + while dest.len() < size { + let mut buf = [0u8; 256]; + let mut buf_len = buf.len(); + let status = unsafe { + nsm_get_random(nsm_fd, buf.as_mut_ptr(), &mut buf_len) + }; + match status { + ErrorCode::Success => { + dest.extend_from_slice(&buf); + }, + _ => { + return Err(SystemError { + message: String::from("Failed to get entropy from NSM device") + }); + } + }; + } + Ok(dest) +} + +// Initialize nitro device +pub fn init_platform(){ + use system::insmod; + // TODO: error handling + nitro_heartbeat(); + + match insmod("/nsm.ko") { + Ok(())=> dmesg(format!("Loaded nsm.ko")), + Err(e)=> eprintln!("{}", e) + }; +} diff --git a/src/init/Cargo.lock b/src/init/Cargo.lock index e14365e..95bbb4a 100644 --- a/src/init/Cargo.lock +++ b/src/init/Cargo.lock @@ -8,6 +8,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "aws" +version = "0.1.0" +dependencies = [ + "aws-nitro-enclaves-nsm-api", + "libc", + "nsm-lib", + "system", +] + [[package]] name = "aws-nitro-enclaves-nsm-api" version = "0.2.1" @@ -98,9 +108,9 @@ dependencies = [ name = "init" version = "0.1.0" dependencies = [ - "aws-nitro-enclaves-nsm-api", + "aws", "libc", - "nsm-lib", + "system", ] [[package]] @@ -268,6 +278,13 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system" +version = "0.1.0" +dependencies = [ + "libc", +] + [[package]] name = "tempfile" version = "3.3.0" diff --git a/src/init/Cargo.toml b/src/init/Cargo.toml index a3c5e27..25b7b47 100644 --- a/src/init/Cargo.toml +++ b/src/init/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [dependencies] libc = "0.2.134" -nsm_lib = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="nsm-lib", optional = false } -nsm_api = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="aws-nitro-enclaves-nsm-api", optional = false } +aws = { path = "../aws"} +system = { path = "../system"} [[bin]] diff --git a/src/init/init.rs b/src/init/init.rs index 18d56c9..01ce125 100644 --- a/src/init/init.rs +++ b/src/init/init.rs @@ -1,223 +1,32 @@ -use libc::{ - c_ulong, - c_int, - c_void, - MS_NOSUID, - MS_NOEXEC, - MS_NODEV, -}; -use std::{ - mem::zeroed, - mem::size_of, - ffi::CString, - fs::{File, read_to_string}, - os::unix::io::AsRawFd, - fmt, -}; +use system::{seed_entropy, reboot, freopen, mount, dmesg}; -struct SystemError { - message: String, -} -impl fmt::Display for SystemError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", boot_time(), self.message) +//TODO: Feature flag +use aws::{init_platform, get_entropy}; + +// Mount common filesystems with conservative permissions +fn init_rootfs() { + use libc::{MS_NOSUID, MS_NOEXEC, MS_NODEV }; + let no_dse = MS_NODEV | MS_NOSUID | MS_NOEXEC; + let no_se = MS_NOSUID | MS_NOEXEC; + let args = [ + ("devtmpfs", "/dev", "devtmpfs", no_se, "mode=0755"), + ("devtmpfs", "/dev", "devtmpfs", no_se, "mode=0755"), + ("devpts", "/dev/pts", "devpts", no_se, ""), + ("shm", "/dev/shm", "tmpfs", no_dse, "mode=0755"), + ("proc", "/proc", "proc", no_dse, "hidepid=2"), + ("tmpfs", "/run", "tmpfs", no_dse, "mode=0755"), + ("tmpfs", "/tmp", "tmpfs", no_dse, ""), + ("sysfs", "/sys", "sysfs", no_dse, ""), + ("cgroup_root", "/sys/fs/cgroup", "tmpfs", no_dse, "mode=0755"), + ]; + for (src, target, fstype, flags, data) in args { + match mount(src, target, fstype, flags, data) { + Ok(())=> dmesg(format!("Mounted {}", target)), + Err(e)=> eprintln!("{}", e), + } } } -// Log dmesg formatted log to console -fn dmesg(message: String){ - println!("{} {}", boot_time(), message); -} - -// Dmesg formatted seconds since boot -fn boot_time() -> String { - use libc::{clock_gettime, timespec, CLOCK_BOOTTIME}; - let mut t = timespec { tv_sec: 0, tv_nsec: 0 }; - unsafe { clock_gettime(CLOCK_BOOTTIME, &mut t as *mut timespec); } - format!("[ {: >4}.{}]", t.tv_sec, t.tv_nsec / 1000).to_string() -} - -fn reboot(){ - use libc::{reboot, RB_AUTOBOOT}; - unsafe { - reboot(RB_AUTOBOOT); - } -} - -// libc::mount casting/error wrapper -fn mount( - src: &str, - target: &str, - fstype: &str, - flags: c_ulong, - data: &str, -) -> Result<(), SystemError> { - use libc::mount; - let src_cs = CString::new(src).unwrap(); - let fstype_cs = CString::new(fstype).unwrap(); - let data_cs = CString::new(data).unwrap(); - let target_cs = CString::new(target).unwrap(); - if unsafe { - mount( - src_cs.as_ptr(), - target_cs.as_ptr(), - fstype_cs.as_ptr(), - flags, - data_cs.as_ptr() as *const c_void - ) - } != 0 { - Err(SystemError { message: format!("Failed to mount: {}", target) }) - } else { - Ok(()) - } -} - -// libc::freopen casting/error wrapper -fn freopen( - filename: &str, - mode: &str, - file: c_int, -) -> Result<(), SystemError> { - use libc::{freopen, fdopen}; - let filename_cs = CString::new(filename).unwrap(); - let mode_cs = CString::new(mode).unwrap(); - if unsafe { - freopen( - filename_cs.as_ptr(), - mode_cs.as_ptr(), - fdopen(file, mode_cs.as_ptr() as *const i8) - ) - }.is_null() { - Err(SystemError { message: format!("Failed to freopen: {}", filename) }) - } else { - Ok(()) - } -} - -// Insert kernel module into memory -fn insmod(path: &str) -> Result<(), SystemError> { - use libc::{syscall, SYS_finit_module}; - let file = File::open(path).unwrap(); - let fd = file.as_raw_fd(); - if unsafe { syscall(SYS_finit_module, fd, &[0u8; 1], 0) } < 0 { - Err(SystemError { - message: format!("Failed to insert kernel module: {}", path) - }) - } else { - Ok(()) - } -} - -// Instantiate a socket -fn socket_connect( - family: c_int, - port: u32, - cid: u32, -) -> Result { - use libc::{connect, socket, sockaddr, sockaddr_vm, SOCK_STREAM}; - let fd = unsafe { socket(family, SOCK_STREAM, 0) }; - if unsafe { - let mut sa: sockaddr_vm = zeroed(); - sa.svm_family = family as _; - sa.svm_port = port; - sa.svm_cid = cid; - connect( - fd, - &sa as *const _ as *mut sockaddr, - size_of::() as _, - ) - } < 0 { - Err(SystemError { - message: format!("Failed to connect to socket: {}", family) - }) - } else { - Ok(fd) - } -} - -// Signal to Nitro hypervisor that booting was successful -fn nitro_heartbeat() { - use libc::{write, read, close, AF_VSOCK}; - let mut buf: [u8; 1] = [0; 1]; - buf[0] = 0xB7; // AWS Nitro heartbeat value - let fd = match socket_connect(AF_VSOCK, 9000, 3) { - Ok(f)=> f, - Err(e)=> { - eprintln!("{}", e); - return - }, - }; - unsafe { - write(fd, buf.as_ptr() as _, 1); - read(fd, buf.as_ptr() as _, 1); - close(fd); - } - dmesg(format!("Sent NSM heartbeat")); -} - -// Get entropy sample from Nitro device -fn nitro_get_entropy() -> Result<[u8; 256], SystemError> { - use nsm_api::api::ErrorCode; - use nsm_lib::{nsm_get_random, nsm_lib_init}; - - let nsm_fd = nsm_lib_init(); - if nsm_fd < 0 { - return Err(SystemError { - message: String::from("Failed to connect to NSM device") - }); - }; - - let mut dest = [0u8; 256]; - let mut dest_len = dest.len(); - - let status = unsafe { - nsm_get_random(nsm_fd, dest.as_mut_ptr(), &mut dest_len) - }; - match status { - ErrorCode::Success => { - Ok(dest) - }, - _ => Err(SystemError { - message: String::from("Failed to get entropy from NSM device") - }) - } -} - -fn get_random_poolsize() -> Result { - let ps_path = "/proc/sys/kernel/random/poolsize"; - let size_s = read_to_string(ps_path).unwrap_or_else(|_| String::new()); - if size_s.is_empty(){ - return Err(SystemError { - message: String::from("Failed to read kernel random poolsize"), - }) - }; - match size_s.parse::() { - Ok(size) => Ok(size), - Err(_) => Err(SystemError { - message: String::from("Failed to parse kernel random poolsize"), - }), - } -} - -// Initialize nitro device -fn init_nitro(){ - // TODO: error handling - nitro_heartbeat(); - - match insmod("/nsm.ko") { - Ok(())=> dmesg(format!("Loaded nsm.ko")), - Err(e)=> eprintln!("{}", e) - }; - match get_random_poolsize() { - Ok(size)=> dmesg(format!("Kernel entropy pool size: {}", size)), - Err(e)=> eprintln!("{}", e) - }; - match nitro_get_entropy() { - Ok(_)=> dmesg(format!("Got NSM Entropy sample")), - Err(e)=> eprintln!("{}", e) - }; -} - // Initialize console with stdin/stdout/stderr fn init_console() { let args = [ @@ -233,31 +42,14 @@ fn init_console() { } } -// Mount common filesystems with conservative permissions -fn init_rootfs() { - let args = [ - ("devtmpfs", "/dev", "devtmpfs", MS_NOSUID | MS_NOEXEC, "mode=0755"), - ("devtmpfs", "/dev", "devtmpfs", MS_NOSUID | MS_NOEXEC, "mode=0755"), - ("proc", "/proc", "proc", MS_NODEV | MS_NOSUID | MS_NOEXEC, "hidepid=2"), - ("tmpfs", "/run", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC, "mode=0755"), - ("tmpfs", "/tmp", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC, ""), - ("shm", "/dev/shm", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC, "mode=0755"), - ("devpts", "/dev/pts", "devpts", MS_NOSUID | MS_NOEXEC, ""), - ("sysfs", "/sys", "sysfs", MS_NODEV | MS_NOSUID | MS_NOEXEC, ""), - ("cgroup_root", "/sys/fs/cgroup", "tmpfs", MS_NODEV | MS_NOSUID | MS_NOEXEC, "mode=0755"), - ]; - for (src, target, fstype, flags, data) in args { - match mount(src, target, fstype, flags, data) { - Ok(())=> dmesg(format!("Mounted {}", target)), - Err(e)=> eprintln!("{}", e), - } - } -} - fn boot(){ init_rootfs(); init_console(); - init_nitro(); + init_platform(); + match seed_entropy(4096, get_entropy) { + Ok(size)=> dmesg(format!("Seeded kernel with entropy: {}", size)), + Err(e)=> eprintln!("{}", e) + }; } fn main() { diff --git a/src/system/Cargo.toml b/src/system/Cargo.toml new file mode 100644 index 0000000..c4fbcc6 --- /dev/null +++ b/src/system/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "system" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2.134" diff --git a/src/system/src/lib.rs b/src/system/src/lib.rs new file mode 100644 index 0000000..3d90f68 --- /dev/null +++ b/src/system/src/lib.rs @@ -0,0 +1,169 @@ +use libc::{ c_ulong, c_int, c_void }; +use std::{ + mem::{zeroed, size_of}, + ffi::CString, + fs::File, + os::unix::io::AsRawFd, + fmt, +}; + +pub struct SystemError { + pub message: String, +} +impl fmt::Display for SystemError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", boot_time(), self.message) + } +} + +// Log dmesg formatted log to console +pub fn dmesg(message: String){ + println!("{} {}", boot_time(), message); +} + +// Dmesg formatted seconds since boot +pub fn boot_time() -> String { + use libc::{clock_gettime, timespec, CLOCK_BOOTTIME}; + let mut t = timespec { tv_sec: 0, tv_nsec: 0 }; + unsafe { clock_gettime(CLOCK_BOOTTIME, &mut t as *mut timespec); } + format!("[ {: >4}.{}]", t.tv_sec, t.tv_nsec / 1000).to_string() +} + +// Unconditionally reboot the system now +pub fn reboot(){ + use libc::{reboot, RB_AUTOBOOT}; + unsafe { + reboot(RB_AUTOBOOT); + } +} + +// libc::mount casting/error wrapper +pub fn mount( + src: &str, + target: &str, + fstype: &str, + flags: c_ulong, + data: &str, +) -> Result<(), SystemError> { + use libc::mount; + let src_cs = CString::new(src).unwrap(); + let fstype_cs = CString::new(fstype).unwrap(); + let data_cs = CString::new(data).unwrap(); + let target_cs = CString::new(target).unwrap(); + if unsafe { + mount( + src_cs.as_ptr(), + target_cs.as_ptr(), + fstype_cs.as_ptr(), + flags, + data_cs.as_ptr() as *const c_void + ) + } != 0 { + Err(SystemError { message: format!("Failed to mount: {}", target) }) + } else { + Ok(()) + } +} + +// libc::freopen casting/error wrapper +pub fn freopen( + filename: &str, + mode: &str, + file: c_int, +) -> Result<(), SystemError> { + use libc::{freopen, fdopen}; + let filename_cs = CString::new(filename).unwrap(); + let mode_cs = CString::new(mode).unwrap(); + if unsafe { + freopen( + filename_cs.as_ptr(), + mode_cs.as_ptr(), + fdopen(file, mode_cs.as_ptr() as *const i8) + ) + }.is_null() { + Err(SystemError { message: format!("Failed to freopen: {}", filename) }) + } else { + Ok(()) + } +} + +// Insert kernel module into memory +pub fn insmod(path: &str) -> Result<(), SystemError> { + use libc::{syscall, SYS_finit_module}; + let file = File::open(path).unwrap(); + let fd = file.as_raw_fd(); + if unsafe { syscall(SYS_finit_module, fd, &[0u8; 1], 0) } < 0 { + Err(SystemError { + message: format!("Failed to insert kernel module: {}", path) + }) + } else { + Ok(()) + } +} + +// Instantiate a socket +pub fn socket_connect( + family: c_int, + port: u32, + cid: u32, +) -> Result { + use libc::{connect, socket, sockaddr, sockaddr_vm, SOCK_STREAM}; + let fd = unsafe { socket(family, SOCK_STREAM, 0) }; + if unsafe { + let mut sa: sockaddr_vm = zeroed(); + sa.svm_family = family as _; + sa.svm_port = port; + sa.svm_cid = cid; + connect( + fd, + &sa as *const _ as *mut sockaddr, + size_of::() as _, + ) + } < 0 { + Err(SystemError { + message: format!("Failed to connect to socket: {}", family) + }) + } else { + Ok(fd) + } +} + +// Seed an entropy sample into the kernel randomness pool. +pub fn seed_entropy( + size: usize, + source: fn(usize) -> Result, SystemError>, +) -> Result { + use std::io::Write; + + let entropy_sample = match source(size) { + Ok(sample)=> sample, + Err(e)=> { return Err(e) }, + }; + + use std::fs::OpenOptions; + let mut random_fd = match OpenOptions::new() + .read(true) + .write(true) + .open("/dev/urandom") + { + Ok(file) => file, + Err(_) => { + return Err(SystemError { + message: String::from("Failed to open /dev/urandom"), + }); + }, + }; + + // 5.10+ kernel entropy pools are made of BLAKE2 hashes fixed at 256 bit + // The RNDADDENTROPY crediting system is now complexity with no gain. + // We just simply write samples to /dev/urandom now. + // See: https://cdn.kernel.org/pub/linux/kernel/v5.x/ChangeLog-5.10.119 + match random_fd.write_all(&entropy_sample) { + Ok(()) => Ok(entropy_sample.len()), + Err(_) => { + return Err(SystemError { + message: String::from("Failed to write to /dev/urandom"), + }); + } + } +}