Merge EnclaveOS => Nit refactor

This commit is contained in:
Ryan Heywood 2025-07-29 00:13:57 -04:00
parent 3005596663
commit 1a1a0bf3e8
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
24 changed files with 831 additions and 314 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
.git
out
Containerfile

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# ---> Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# These artifacts are made when using Docker
out/

View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "libc"
@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "system"
name = "nit"
version = "0.1.0"
dependencies = [
"libc",

5
Cargo.toml Normal file
View File

@ -0,0 +1,5 @@
[workspace]
resolver = "2"
members = [
"crates/nit",
]

View File

@ -2,15 +2,16 @@ FROM stagex/pallet-rust@sha256:740b9ed5f2a897d45cafdc806976d84231aa50a6499861075
ENV TARGET=x86_64-unknown-linux-musl
ENV RUSTFLAGS="-C target-feature=+crt-static"
ENV CARGOFLAGS="--locked --no-default-features --release --target ${TARGET}"
ENV CARGOFLAGS="--locked --all-features --release --target ${TARGET}"
ENV CARGO_TARGET_DIR=/cargo-target
ADD . /src
WORKDIR /src/init
WORKDIR /src
RUN cargo build ${CARGOFLAGS}
WORKDIR /rootfs
RUN cp /src/init/target/${TARGET}/release/init .
RUN install -Dm755 /cargo-target/${TARGET}/release/nit /rootfs/usr/bin/init
FROM scratch as package
COPY --from=build /rootfs .

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2025 Distrust
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -6,8 +6,8 @@ out:
out/init: out
docker build \
--tag local/init \
--tag local/nit \
--progress=plain \
--output type=local,rewrite-timestamp=true,dest=out\
-f Containerfile \
src/
.

32
README.md Normal file
View File

@ -0,0 +1,32 @@
# nit
25% smaller than init
## Testing with AWS on Amazon Linux
Ensure the instance is set up with Nitro Enclaves, and run the following
command to install and enable nitro enclave support:
```sh
# You can leave off `git` if you've already installed it to clone the repo.
sudo dnf install aws-nitro-enclaves-cli make git
sudo systemctl enable --now nitro-enclaves-allocator.service
sudo systemctl enable --now docker.service
# Add the current user to the Docker group
sudo usermod -aG docker $USER
```
You may need to reconnect to change the signed-in users. Use `groups` to see if
you are in the `docker` group.
Then, the EIF can be built using:
```sh
make
```
And tested using:
```sh
sudo make run
```

12
crates/nit/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "nit"
version = "0.1.0"
edition = "2024"
license = "MIT"
[features]
aws = []
default = ["aws"]
[dependencies]
libc = "0.2.172"

45
crates/nit/src/config.rs Normal file
View File

@ -0,0 +1,45 @@
use crate::platform::{self, Platform};
use crate::result::{Context, Result};
#[derive(Debug)]
pub enum Mode {
Spawn,
Exec,
}
#[derive(Debug)]
pub struct Config {
pub platform: Option<Box<dyn Platform>>,
pub mode: Mode,
pub target: String,
}
pub fn get_config() -> Result<Config> {
let mut values = std::collections::HashMap::<&str, String>::new();
let cmdline = std::fs::read_to_string("/proc/cmdline")
.context(format_args!("could not read kernel cmdline"))?;
for word in cmdline.split_whitespace() {
if let Some((lhs, rhs)) = word.split_once('=') {
if let Some(("nit", name)) = lhs.split_once('.') {
values.insert(name, rhs.to_string());
}
}
}
let mode = if let Some(mode) = values.remove("mode") {
match &*mode {
"spawn" => Mode::Spawn,
"exec" => Mode::Exec,
m => panic!("Bad mode: {m}"),
}
} else {
Mode::Exec
};
let platform = platform::get_current_platform(values.remove("platform").as_deref())?;
let target = values.remove("target").unwrap_or(String::from("/usr/bin/hello"));
Ok(Config { platform, mode, target })
}

86
crates/nit/src/main.rs Normal file
View File

@ -0,0 +1,86 @@
mod config;
mod platform;
mod result;
mod system;
use result::Result;
use system::dmesg;
fn main() {
if let Err(e) = init() {
dmesg(format!("Error: {e}"));
let mut opt = std::error::Error::source(&e);
while let Some(current_source) = opt {
dmesg(format!("Caused by: {current_source}"));
opt = current_source.source();
}
}
}
extern "C" fn handle_sigchld(_sig: i32) {
// wait on all dead processes
while system::syscall::waitpid(-1, libc::WNOHANG).is_ok() {
// pass
}
}
fn init() -> Result<()> {
if let Err(errors) = system::mount_default_targets() {
for error in errors {
dmesg(format!("Error while mounting: {error}"));
let mut opt = std::error::Error::source(&error);
while let Some(current_source) = opt {
dmesg(format!("Caused by: {current_source}"));
opt = current_source.source();
}
}
}
init_console()?;
let config = config::get_config()?;
if let Some(platform) = config.platform.as_deref() {
platform::init(platform)?;
} else if let Some(platform) = platform::get_current_platform(None)?.as_deref() {
platform::init(platform)?;
}
system::dmesg("EnclaveOS Booted");
let command = &config.target;
match config.mode {
config::Mode::Spawn => {
panic!("Spawn mode has not been tested.")
/*
// set up a process reaper. any time a child process dies, a SIGCHLD will be fired, and
// the signal handler will reap the processes
eprintln!("installing signal handler");
system::syscall::signal(libc::SIGCHLD, handle_sigchld)?;
loop {
eprintln!("spawning {command}");
if let Err(e) = std::process::Command::new(command).spawn() {
eprintln!("Encountered error running {command}: {e}");
}
}
*/
}
config::Mode::Exec => {
dmesg(format!("pivoting to {command}"));
system::syscall::execv(command, &[])?;
}
}
Ok(())
}
fn init_console() -> Result<(), result::CtxError> {
for (filename, mode, fd) in [
("/dev/console", "r", 0),
("/dev/console", "w", 1),
("/dev/console", "w", 2),
] {
system::syscall::freopen(filename, mode, &fd)?;
}
Ok(())
}

View File

@ -0,0 +1,74 @@
use crate::{
result::{Context, Result},
system::dmesg,
};
#[derive(Debug)]
pub struct Aws;
#[derive(Debug)]
pub struct InvalidHeartbeatResponse(u8);
impl std::fmt::Display for InvalidHeartbeatResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Invalid heartbeat response: expected 0xB7, got 0x{}",
self.0
)
}
}
impl std::error::Error for InvalidHeartbeatResponse {}
impl Aws {
fn init_heartbeat() -> Result<()> {
use crate::system::syscall::{
close, connect, read, sockaddr_vm, socket, write, SocketFamily, SocketType,
};
let mut sockaddr: sockaddr_vm = sockaddr_vm {
svm_family: SocketFamily::Vsock as u16,
svm_port: 9000,
svm_cid: 3,
svm_reserved1: 0,
svm_zero: [0; 4],
};
let mut buf = [0xB7];
let fd = socket(SocketFamily::Vsock, SocketType::Stream)?;
unsafe {
connect(
fd,
std::ptr::from_mut(&mut sockaddr).cast(),
std::mem::size_of_val(&sockaddr),
)?;
};
dmesg("heartbeat =>");
write(fd, &buf)?;
read(fd, &mut buf)?;
close(fd)?;
if buf[0] != 0xB7 {
return Err(InvalidHeartbeatResponse(buf[0]))
.context(format_args!("Bad value from heartbeat"));
}
dmesg("<= heartbeat");
Ok(())
}
}
impl super::Platform for Aws {
fn is(&self) -> Result<bool> {
std::fs::exists("/dev/nsm").context(format_args!("could not check if /dev/nsm exists"))
}
fn init(&self) -> Result<()> {
Self::init_heartbeat()?;
// enclaveos_shim::init_platform();
Ok(())
}
}

View File

@ -0,0 +1,78 @@
use crate::{
result::Result,
system::{self, Mount},
};
pub trait Platform: std::fmt::Debug {
/// Whether the current Platform is the `Self` platform.
///
/// This method should check for the existence of hardware devices that irrefutably defines
/// this platform as the given platform. For instance, the existence of `/dev/n`
fn is(&self) -> Result<bool>;
/// Return the names and parameters for any required kernel modules.
fn get_modules(&self) -> Result<Vec<(String, String)>> {
Ok(vec![])
}
/// The configuration for mounting filesystems for the platform.
///
/// Filesystems such as `/proc` and `/dev` have already been mounted. This method should be
/// used to define additional mounts.
fn get_mounts(&self) -> Result<Vec<Mount>> {
Ok(vec![])
}
/// Initialize all necessary requirements for the platform.
fn init(&self) -> Result<()> {
Ok(())
}
}
fn init_filesystems(iter: impl IntoIterator<Item = Mount>) -> Result<()> {
for mount in iter {
mount.mount_self()?;
}
Ok(())
}
fn init_modules(iter: impl IntoIterator<Item = (String, String)>) -> Result<()> {
for (module, params) in iter {
system::insmod(&module, &params)?;
}
Ok(())
}
#[cfg(feature = "aws")]
pub mod aws;
pub fn get_current_platform(name: Option<&str>) -> Result<Option<Box<dyn Platform>>> {
#[allow(clippy::collapsible_match)]
if let Some(name) = name {
match name {
#[cfg(feature = "aws")]
"aws" => return Ok(Some(Box::new(aws::Aws))),
_ => {}
}
}
#[cfg(feature = "aws")]
if aws::Aws.is()? {
return Ok(Some(Box::new(aws::Aws)));
}
Ok(None)
}
pub fn init(platform: &dyn Platform) -> Result<()> {
// TODO: Error handling strategy: If a platform is compiled in and loaded, if platform
// specific error handling doesn't work, fall back to generic?
platform.get_mounts().and_then(init_filesystems)?;
platform.get_modules().and_then(init_modules)?;
platform.init()?;
Ok(())
}

52
crates/nit/src/result.rs Normal file
View File

@ -0,0 +1,52 @@
//! Error handling niceties.
// Because who needs anyhow anyways.
/// Provide context for an existing error.
pub trait Context<T> {
fn context(self, fmt: std::fmt::Arguments<'_>) -> Result<T, CtxError>;
}
impl<T, E> Context<T> for std::result::Result<T, E>
where
E: std::error::Error + 'static,
{
fn context(self, fmt: std::fmt::Arguments<'_>) -> Result<T, CtxError> {
match self {
Ok(o) => Ok(o),
Err(e) => Err(CtxError {
context: fmt.to_string(),
inner: Box::new(e),
}),
}
}
}
/// An error with context attached.
///
/// The context is provided as format arguments, but will be constructed into a string if the value
/// is an error.
#[derive(Debug)]
pub struct CtxError {
context: String,
inner: Box<dyn std::error::Error>,
}
impl std::fmt::Display for CtxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.context.as_str())
}
}
impl std::error::Error for CtxError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.inner.as_ref())
}
}
pub type Result<T, E = CtxError> = std::result::Result<T, E>;
pub fn ctx_os_error(args: std::fmt::Arguments<'_>) -> Result<(), CtxError> {
Err(std::io::Error::last_os_error()).context(args)
}

View File

@ -0,0 +1,156 @@
use crate::result::{Context, CtxError, Result};
use libc::{MS_NODEV, MS_NOEXEC, MS_NOSUID};
use std::path::{Path, PathBuf};
pub mod syscall;
pub fn dmesg(value: impl std::fmt::Display) {
let timespec = syscall::clock_gettime(libc::CLOCK_BOOTTIME).unwrap();
for line in value.to_string().lines() {
eprintln!(
"[{: >5}.{}] {line}",
timespec.tv_sec,
timespec.tv_nsec / 1000
);
}
}
pub enum MountType {
DevTmpFs,
DevPts,
Shm,
Proc,
TmpFs,
SysFs,
}
impl From<MountType> for PathBuf {
fn from(value: MountType) -> Self {
Self::from(value.as_str())
}
}
impl MountType {
fn as_str(&self) -> &'static str {
match self {
MountType::DevTmpFs => "devtmpfs",
MountType::DevPts => "devpts",
MountType::Shm => "shm",
MountType::Proc => "proc",
MountType::TmpFs => "tmpfs",
MountType::SysFs => "sysfs",
}
}
}
pub struct Mount {
source: PathBuf,
target: PathBuf,
fstype: MountType,
flags: libc::c_ulong,
data: Option<String>,
}
impl Mount {
pub fn new<'a>(
source: impl Into<PathBuf>,
target: impl Into<PathBuf>,
fstype: MountType,
flags: libc::c_ulong,
data: impl Into<Option<&'a str>>,
) -> Self {
let source = source.into();
let target = target.into();
let data = data.into().map(Into::into);
Self {
source,
target,
fstype,
flags,
data,
}
}
pub fn mount_self(&self) -> Result<()> {
if !std::fs::exists(&self.target).context(format_args!(
"could not check if path exists: {path}",
path = self.target.display()
))? {
dmesg(format!(
"Making directory: {target}",
target = self.target.display()
));
syscall::mkdir(&self.target)?;
}
dmesg(format!(
"Mounting {source} to {target}",
source = self.source.display(),
target = self.target.display()
));
syscall::mount(
&self.source,
&self.target,
self.fstype.as_str(),
self.flags,
self.data.as_deref(),
)?;
Ok(())
}
}
#[allow(clippy::similar_names)]
pub fn mount_default_targets() -> Result<(), Vec<CtxError>> {
let no_dse = MS_NODEV | MS_NOSUID | MS_NOEXEC;
let no_se = MS_NOSUID | MS_NOEXEC;
let m0755 = Some("mode=0755");
let hidepid = Some("hidepid=2");
// why, oh why, rustfmt
let mounts = [
Mount::new(
MountType::DevTmpFs,
"/dev",
MountType::DevTmpFs,
no_se,
m0755,
),
Mount::new(MountType::Proc, "/proc", MountType::Proc, no_dse, hidepid),
Mount::new(MountType::TmpFs, "/tmp", MountType::TmpFs, no_dse, None),
Mount::new(MountType::SysFs, "/sys", MountType::SysFs, no_dse, None),
Mount::new(
"cgroup_root",
"/sys/fs/cgroup",
MountType::TmpFs,
no_dse,
m0755,
),
];
let mut errors = vec![];
for mount in mounts {
if let Err(e) = mount.mount_self() {
errors.push(e);
}
}
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
pub fn insmod(path: impl AsRef<Path>, params: impl AsRef<str>) -> Result<()> {
let path = path.as_ref();
let params = params.as_ref();
let file = std::fs::File::open(path).context(format_args!(
"could not open: {path}",
path = path.display()
))?;
syscall::finit_module(file, params)?;
Ok(())
}

View File

@ -0,0 +1,257 @@
//! Wrappers for libc system calls and other necessary functions.
//!
//! These functions, for one reason or another, require using the `libc` crate and can't
//! be implemented directly in the standard library.
// Some of these methods might not be called, because support for the features that
// invoke the functions were not enabled.
#![allow(dead_code)]
use crate::result::{ctx_os_error, Context, Result};
use libc::{self, c_int, c_ulong, c_void};
use std::{
ffi::CString,
os::fd::{AsRawFd, RawFd},
path::Path,
};
pub fn clock_gettime(clock_id: libc::clockid_t) -> Result<libc::timespec> {
let mut t = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
match unsafe { libc::clock_gettime(clock_id, std::ptr::from_mut(&mut t)) } {
0 => Ok(t),
-1 => {
// at this point, Err(ctx_os_error()) is starting to seem appealing
ctx_os_error(format_args!("error calling clock_gettime({clock_id}, ...)")).map(|()| {
libc::timespec {
tv_sec: 0,
tv_nsec: 0,
}
})
}
n => unreachable!("clock_gettime() syscall returned bad value: {n}"),
}
}
pub fn mount(
src: impl AsRef<Path>,
target: impl AsRef<Path>,
fstype: impl AsRef<str>,
flags: c_ulong,
data: Option<&str>,
) -> Result<()> {
let src = src.as_ref();
let target = target.as_ref();
let fstype = fstype.as_ref();
// it upsets me to no end that these two lines format differently.
#[rustfmt::ignore]
let src_cstr = CString::new(src.as_os_str().as_encoded_bytes())
.context(format_args!("bad src: {src}", src = src.display()))?;
#[rustfmt::ignore]
let target_cstr = CString::new(target.as_os_str().as_encoded_bytes()).context(format_args!(
"bad target: {target}",
target = target.display()
))?;
let fstype_cs = CString::new(fstype).context(format_args!("bad fstype: {fstype}"))?;
let data_cs = data
.map(CString::new)
.transpose()
.context(format_args!("bad data: {data:?}"))?;
let data_ptr = if let Some(s) = data_cs {
s.as_ptr()
} else {
std::ptr::null()
};
// SAFETY: mount() can accept nullptr for data. no other pointers are constructed manually.
match unsafe {
libc::mount(
src_cstr.as_ptr(),
target_cstr.as_ptr(),
fstype_cs.as_ptr(),
flags,
data_ptr.cast::<c_void>(),
)
} {
0 => Ok(()),
-1 => ctx_os_error(format_args!(
"error calling mount({src}, {target}, {fstype}, {flags}, {data:?})",
src = src.display(),
target = target.display()
)),
n => unreachable!("mount() syscall returned bad value: {n}"),
}
}
pub fn mkdir(path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
let path_cs = CString::new(path.as_os_str().as_encoded_bytes())
.context(format_args!("bad path: {path}", path = path.display()))?;
match unsafe { libc::mkdir(path_cs.as_ptr(), 0o755) } {
0 => Ok(()),
-1 => ctx_os_error(format_args!(
"error calling mkdir({path}, 0o755)",
path = path.display()
)),
n => unreachable!("mount() syscall returned bad value: {n}"),
}
}
pub fn freopen(path: impl AsRef<Path>, mode: impl AsRef<str>, fd: &impl AsRawFd) -> Result<()> {
let path = path.as_ref();
let mode = mode.as_ref();
let fd = fd.as_raw_fd();
#[rustfmt::ignore]
let filename_cs = CString::new(path.as_os_str().as_encoded_bytes())
.context(format_args!("bad path: {path}", path = path.display()))?;
let mode_cs = CString::new(mode).context(format_args!("bad mode: {mode}"))?;
// SAFETY: no pointers are constructed manually.
let file = unsafe { libc::fdopen(fd, mode.as_ptr().cast::<i8>()) };
if file.is_null() {
return ctx_os_error(format_args!("bad fdopen({fd}, {mode})"));
}
// SAFETY: no pointers are constructed manually.
let file = unsafe { libc::freopen(filename_cs.as_ptr(), mode_cs.as_ptr(), file) };
if file.is_null() {
return ctx_os_error(format_args!(
"bad freopen({path}, {mode}, {file:?} as *FILE)",
path = path.display()
));
}
Ok(())
}
// NOTE: We want to consume the file, so we do not allow a ref to impl AsRawFd
#[allow(clippy::needless_pass_by_value)]
pub fn finit_module(fd: impl AsRawFd, params: impl AsRef<str>) -> Result<()> {
let fd = fd.as_raw_fd();
let params = params.as_ref();
let params_cs = CString::new(params).context(format_args!("bad params: {params}"))?;
// SAFETY: no pointers are constructed manually, and AsRawFd should be a proper
// file descriptor.
match unsafe { libc::syscall(libc::SYS_finit_module, fd, params_cs.as_ptr(), 0) } {
0 => Ok(()),
-1 => ctx_os_error(format_args!("error calling finit_module({fd}, {params})")),
n => unreachable!("syscall(SYS_finit_module, ...) returned bad value: {n}"),
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum SocketFamily {
Vsock = libc::AF_VSOCK,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum SocketType {
Stream = libc::SOCK_STREAM,
}
// TODO: allow specifying protocol?
pub fn socket(family: SocketFamily, typ: SocketType) -> Result<RawFd> {
match unsafe { libc::socket(family as i32, typ as i32, 0) } {
-1 => ctx_os_error(format_args!("error calling socket({family:?}, {typ:?})")).map(|()| 0),
fd => Ok(RawFd::from(fd)),
}
}
pub use libc::sockaddr_vm;
// This function is unsafe since we have to pass it a C-style union.
pub unsafe fn connect(fd: RawFd, sockaddr: *mut libc::sockaddr, size: usize) -> Result<()> {
let size = u32::try_from(size).context(format_args!(
"connect(..., size = {size}) has size > {}",
u32::MAX
))?;
match unsafe { libc::connect(fd, sockaddr, size) } {
0 => Ok(()),
-1 => ctx_os_error(format_args!("error calling connect({fd}, ...)")),
n => unreachable!("connect({fd}, ...) returned bad value: {n}"),
}
}
pub fn write(fd: RawFd, bytes: &[u8]) -> Result<usize> {
match unsafe { libc::write(fd, bytes.as_ptr().cast(), bytes.len()) } {
..0 => ctx_os_error(format_args!("error calling write({fd}, ...)")).map(|()| 0),
n @ 0.. => Ok(usize::try_from(n).unwrap()),
}
}
pub fn read(fd: RawFd, buffer: &mut [u8]) -> Result<usize> {
match unsafe { libc::read(fd, buffer.as_mut_ptr().cast(), buffer.len()) } {
..0 => ctx_os_error(format_args!("error calling read({fd}, ...)")).map(|()| 0),
n @ 0.. => Ok(usize::try_from(n).unwrap()),
}
}
pub fn close(fd: RawFd) -> Result<()> {
match unsafe { libc::close(fd) } {
0 => Ok(()),
-1 => ctx_os_error(format_args!("error calling close({fd})")),
n => unreachable!("close({fd}) returned bad value: {n}"),
}
}
pub fn execv(command: impl AsRef<Path>, args: &[&str]) -> Result<()> {
let command = command.as_ref();
let command_cstr = CString::new(command.as_os_str().as_encoded_bytes())
.context(format_args!("bad command: {}", command.display()))?;
// NOTE: command should be the first string, according to execve() documentation.
let mut args_cstr = vec![command_cstr.clone()];
for arg in args {
let cur_arg_cstr = CString::new(arg.as_bytes()).context(format_args!("bad arg: {arg}"))?;
args_cstr.push(cur_arg_cstr);
}
// NOTE: The last arg must be a null pointer, but we can't construct a CString from nullptr, so
// we construct an array of pointers and use that
let args_ptrs: Vec<_> = args_cstr
.iter()
.map(|s| s.as_ptr())
.chain(std::iter::once(std::ptr::null()))
.collect();
if unsafe { libc::execv(command_cstr.as_ptr().cast(), args_ptrs.as_ptr().cast()) } == -1 {
return ctx_os_error(format_args!(
"error calling exec({command}, {args:?})",
command = command.display()
));
}
Ok(())
}
pub type SignalHandler = extern "C" fn(c_int);
pub fn signal(signal: i32, handler: SignalHandler) -> Result<()> {
#[allow(clippy::single_match)]
match unsafe { libc::signal(signal, handler as libc::sighandler_t) } {
libc::SIG_ERR => ctx_os_error(format_args!("invalid handler for {signal:?}")),
_ => Ok(()),
}
}
pub fn waitpid(pid: libc::pid_t, flags: i32) -> Result<libc::pid_t> {
match unsafe { libc::waitpid(pid, std::ptr::null_mut(), flags) } {
pid @ 0.. => Ok(pid),
-1 => ctx_os_error(format_args!("error calling waitpid({pid}, {flags})")).map(|()| 0),
n => unreachable!("waitpid({pid}, {flags}) returned bad value: {n}"),
}
}

24
src/aws/Cargo.lock generated
View File

@ -1,24 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aws"
version = "0.1.0"
dependencies = [
"libc",
"system",
]
[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "system"
version = "0.1.0"
dependencies = [
"libc",
]

View File

@ -1,10 +0,0 @@
[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"
system = { path = "../system"}

View File

@ -1,34 +0,0 @@
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"));
}
// 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)
};
}

33
src/init/Cargo.lock generated
View File

@ -1,33 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aws"
version = "0.1.0"
dependencies = [
"libc",
"system",
]
[[package]]
name = "init"
version = "0.1.0"
dependencies = [
"aws",
"libc",
"system",
]
[[package]]
name = "libc"
version = "0.2.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
[[package]]
name = "system"
version = "0.1.0"
dependencies = [
"libc",
]

View File

@ -1,14 +0,0 @@
[package]
name = "init"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2.134"
aws = { path = "../aws"}
system = { path = "../system"}
[[bin]]
name = "init"
path = "init.rs"

View File

@ -1,54 +0,0 @@
use system::{reboot, freopen, mount, dmesg};
use aws::{init_platform};
// 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),
}
}
}
// Initialize console with stdin/stdout/stderr
fn init_console() {
let args = [
("/dev/console", "r", 0),
("/dev/console", "w", 1),
("/dev/console", "w", 2),
];
for (filename, mode, file) in args {
match freopen(filename, mode, file) {
Ok(())=> {},
Err(e)=> eprintln!("{}", e),
}
}
}
fn boot(){
init_rootfs();
init_console();
init_platform();
}
fn main() {
boot();
dmesg("System booted".to_string());
reboot();
}

View File

@ -1,9 +0,0 @@
[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"

View File

@ -1,129 +0,0 @@
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)
}
}