Compare commits

...

No commits in common. "main" and "refactor-enclaveos-to-nit" have entirely different histories.

22 changed files with 422 additions and 357 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# ---> Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

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

View File

@ -1,16 +0,0 @@
FROM stagex/pallet-rust@sha256:740b9ed5f2a897d45cafdc806976d84231aa50a64998610750b42a48f8daacab as build
ENV TARGET=x86_64-unknown-linux-musl
ENV RUSTFLAGS="-C target-feature=+crt-static"
ENV CARGOFLAGS="--locked --no-default-features --release --target ${TARGET}"
ADD . /src
WORKDIR /src/init
RUN cargo build ${CARGOFLAGS}
WORKDIR /rootfs
RUN cp /src/init/target/${TARGET}/release/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

@ -1,5 +0,0 @@
Copyright 2024 Distrust, Inc.
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,13 +0,0 @@
.PHONY: default
default: out/init
out:
mkdir out
out/init: out
docker build \
--tag local/init \
--progress=plain \
--output type=local,rewrite-timestamp=true,dest=out\
-f Containerfile \
src/

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# nit
25% smaller than init

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();
}

23
src/main.rs Normal file
View File

@ -0,0 +1,23 @@
mod result;
mod system;
mod platform;
use result::Result;
fn main() {
if let Err(e) = init() {
eprintln!("Error: {e}");
let mut opt = Some(&e as &dyn std::error::Error);
while let Some(current_source) = opt {
eprintln!("Caused by: {current_source}");
opt = current_source.source();
}
}
}
fn init() -> Result<()> {
platform::init()?;
println!("Hello, world!");
Ok(())
}

26
src/platform/aws.rs Normal file
View File

@ -0,0 +1,26 @@
use crate::result::{Result, Context};
pub struct Aws;
impl Aws {
fn init_heartbeat() -> Result<()> {
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 get_modules(&self) -> Result<Vec<(String, String)>> {
Ok(vec![
("/nsm.ko".into(), String::new())
])
}
fn init(&self) -> Result<()> {
Self::init_heartbeat()?;
Ok(())
}
}

87
src/platform/mod.rs Normal file
View File

@ -0,0 +1,87 @@
use crate::{system::{self, Mount, MountType}, result::Result};
trait Platform {
/// 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.
///
/// This normally includes filesystems such as `/dev` and `/proc` that are not
/// backed by physical media.
fn get_mounts(&self) -> Result<Vec<Mount>> {
use libc::{MS_NODEV, MS_NOEXEC, MS_NOSUID};
use MountType::{DevPts, DevTmpFs, Proc, Shm, SysFs, TmpFs};
let no_se = MS_NOSUID | MS_NOEXEC;
let no_dse = no_se | MS_NODEV;
let m755 = Some("mode=0755");
Ok(vec![
Mount::new(DevTmpFs, "/dev", DevTmpFs, no_se, m755),
Mount::new(DevPts, "/dev/pts", DevPts, no_se, None),
Mount::new(Shm, "/dev/shm", TmpFs, no_dse, m755),
Mount::new(Proc, "/proc", Proc, no_dse, m755),
Mount::new(TmpFs, "/tmp", TmpFs, no_dse, None),
Mount::new(SysFs, "/sys", SysFs, no_dse, None),
Mount::new("cgroup_root", "/sys/fs/cgroup", TmpFs, no_dse, m755),
])
}
/// 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")]
mod aws;
fn get_current_platform() -> Result<Option<Box<dyn Platform>>> {
#[cfg(feature = "aws")]
if aws::Aws.is()? {
return Ok(Some(Box::new(aws::Aws)))
}
Ok(None)
}
pub fn init() -> Result<()> {
// Error handling strategy: If a platform is compiled in and loaded, if platform
// specific error handling doesn't work, fall back to generic.
let platform = get_current_platform()?;
if let Some(platform) = platform {
// NOTE: We need to make get_mounts _additional_ beyond a base set.
// We need `/dev/nsm` to exist so Aws.is() works.
platform.get_mounts().and_then(init_filesystems)?;
platform.get_modules().and_then(init_modules)?;
platform.init()?;
}
Ok(())
}

52
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)
}

16
src/system/Cargo.lock generated
View File

@ -1,16 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[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,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"

85
src/system/mod.rs Normal file
View File

@ -0,0 +1,85 @@
use crate::result::{Result, Context};
use std::path::{PathBuf, Path};
pub mod syscall;
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<()> {
syscall::mount(
&self.source,
&self.target,
self.fstype.as_str(),
self.flags,
self.data.as_deref(),
)?;
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

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

109
src/system/syscall.rs Normal file
View File

@ -0,0 +1,109 @@
//! 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_ulong, c_void};
use std::{
ffi::CString,
os::fd::AsRawFd,
path::Path,
};
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_ptr = if let Some(s) = data {
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 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}"),
}
}