Merge EnclaveOS => Nit refactor
This commit is contained in:
parent
3005596663
commit
1a1a0bf3e8
|
@ -0,0 +1,3 @@
|
|||
.git
|
||||
out
|
||||
Containerfile
|
|
@ -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/
|
|
@ -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",
|
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/nit",
|
||||
]
|
|
@ -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 .
|
||||
|
|
|
@ -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.
|
4
Makefile
4
Makefile
|
@ -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/
|
||||
.
|
||||
|
|
|
@ -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
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "nit"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[features]
|
||||
aws = []
|
||||
default = ["aws"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.172"
|
|
@ -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 })
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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, ¶ms)?;
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
|
@ -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}"),
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
]
|
|
@ -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"}
|
|
@ -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)
|
||||
};
|
||||
}
|
|
@ -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",
|
||||
]
|
|
@ -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"
|
|
@ -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();
|
||||
}
|
|
@ -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"
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue