Refactor init into modules, and implement simple/working entropy seeding

This commit is contained in:
Lance Vick 2022-10-23 15:15:23 -07:00
parent 70161ec6fe
commit 7e8eda88f7
Signed by: lrvick
GPG Key ID: 8E47A1EC35A1551D
7 changed files with 306 additions and 242 deletions

12
src/aws/Cargo.toml Normal file
View File

@ -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"}

65
src/aws/src/lib.rs Normal file
View File

@ -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<Vec<u8>, 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)
};
}

21
src/init/Cargo.lock generated
View File

@ -8,6 +8,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws"
version = "0.1.0"
dependencies = [
"aws-nitro-enclaves-nsm-api",
"libc",
"nsm-lib",
"system",
]
[[package]] [[package]]
name = "aws-nitro-enclaves-nsm-api" name = "aws-nitro-enclaves-nsm-api"
version = "0.2.1" version = "0.2.1"
@ -98,9 +108,9 @@ dependencies = [
name = "init" name = "init"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"aws-nitro-enclaves-nsm-api", "aws",
"libc", "libc",
"nsm-lib", "system",
] ]
[[package]] [[package]]
@ -268,6 +278,13 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "system"
version = "0.1.0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.3.0" version = "3.3.0"

View File

@ -5,8 +5,8 @@ edition = "2021"
[dependencies] [dependencies]
libc = "0.2.134" libc = "0.2.134"
nsm_lib = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="nsm-lib", optional = false } aws = { path = "../aws"}
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"}
[[bin]] [[bin]]

View File

@ -1,221 +1,30 @@
use libc::{ use system::{seed_entropy, reboot, freopen, mount, dmesg};
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,
};
struct SystemError { //TODO: Feature flag
message: String, use aws::{init_platform, get_entropy};
}
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 // Mount common filesystems with conservative permissions
fn dmesg(message: String){ fn init_rootfs() {
println!("{} {}", boot_time(), message); use libc::{MS_NOSUID, MS_NOEXEC, MS_NODEV };
} let no_dse = MS_NODEV | MS_NOSUID | MS_NOEXEC;
let no_se = MS_NOSUID | MS_NOEXEC;
// Dmesg formatted seconds since boot let args = [
fn boot_time() -> String { ("devtmpfs", "/dev", "devtmpfs", no_se, "mode=0755"),
use libc::{clock_gettime, timespec, CLOCK_BOOTTIME}; ("devtmpfs", "/dev", "devtmpfs", no_se, "mode=0755"),
let mut t = timespec { tv_sec: 0, tv_nsec: 0 }; ("devpts", "/dev/pts", "devpts", no_se, ""),
unsafe { clock_gettime(CLOCK_BOOTTIME, &mut t as *mut timespec); } ("shm", "/dev/shm", "tmpfs", no_dse, "mode=0755"),
format!("[ {: >4}.{}]", t.tv_sec, t.tv_nsec / 1000).to_string() ("proc", "/proc", "proc", no_dse, "hidepid=2"),
} ("tmpfs", "/run", "tmpfs", no_dse, "mode=0755"),
("tmpfs", "/tmp", "tmpfs", no_dse, ""),
fn reboot(){ ("sysfs", "/sys", "sysfs", no_dse, ""),
use libc::{reboot, RB_AUTOBOOT}; ("cgroup_root", "/sys/fs/cgroup", "tmpfs", no_dse, "mode=0755"),
unsafe { ];
reboot(RB_AUTOBOOT); for (src, target, fstype, flags, data) in args {
match mount(src, target, fstype, flags, data) {
Ok(())=> dmesg(format!("Mounted {}", target)),
Err(e)=> eprintln!("{}", e),
} }
} }
// 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<c_int, SystemError> {
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::<sockaddr_vm>() 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<usize, SystemError> {
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::<usize>() {
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 // Initialize console with stdin/stdout/stderr
@ -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(){ fn boot(){
init_rootfs(); init_rootfs();
init_console(); 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() { fn main() {

9
src/system/Cargo.toml Normal file
View File

@ -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"

169
src/system/src/lib.rs Normal file
View File

@ -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<c_int, SystemError> {
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::<sockaddr_vm>() 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<Vec<u8>, SystemError>,
) -> Result<usize, SystemError> {
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"),
});
}
}
}