#![no_std]
mod syscalls;
const MFD_CLOEXEC: u8 = 0x1;
#[used]
pub static EMPTY_STRING: [u8; 8] = [0; 8];
#[derive(Debug)]
pub enum RunError {
FdCreationFailed(i32),
BytesNotWritten(usize, usize),
ExecError(i32),
ForkError(i32),
WaitError(i32),
InvalidElfFormat,
TooManyArgs,
TooManyEnvVars,
ArgTooLong,
EnvVarTooLong,
}
const MAX_ARGS: usize = 32;
const MAX_ENV: usize = 64;
const MAX_STRING_LEN: usize = 256;
#[derive(Clone, Default)]
pub struct RunOptions<'a> {
replace: bool,
args: Option<&'a [&'a str]>,
env: Option<&'a [&'a str]>,
argv0: Option<&'a str>,
}
impl<'a> RunOptions<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn with_replace(mut self, replace: bool) -> Self {
self.replace = replace;
self
}
pub fn with_args(mut self, args: &'a [&'a str]) -> Self {
self.args = Some(args);
self
}
pub fn with_env(mut self, env: &'a [&'a str]) -> Self {
self.env = Some(env);
self
}
pub fn with_argv0(mut self, argv0: &'a str) -> Self {
self.argv0 = Some(argv0);
self
}
}
pub fn run<B: AsRef<[u8]>>(bytes: B) -> Result<i32, RunError> {
run_with_options(bytes, RunOptions::default())
}
pub fn run_with_options<B: AsRef<[u8]>>(
bytes: B,
options: RunOptions<'_>,
) -> Result<i32, RunError> {
let fd = create_fd()?;
let bytes = bytes.as_ref();
write_bytes(fd, bytes)?;
execute(fd, options)
}
fn create_fd() -> Result<u16, RunError> {
let fd = unsafe { syscalls::memfd_create(EMPTY_STRING, MFD_CLOEXEC as u32) };
if fd == -1 {
return Err(RunError::FdCreationFailed(-1)); }
Ok(fd as _)
}
fn validate_elf_header(bytes: &[u8]) -> bool {
if bytes.len() < 16 {
return false;
}
bytes[0] == 0x7f && bytes[1] == b'E' && bytes[2] == b'L' && bytes[3] == b'F'
}
fn write_bytes(fd: u16, bytes: &[u8]) -> Result<(), RunError> {
if !validate_elf_header(bytes) {
unsafe { syscalls::close(fd as i32) };
return Err(RunError::InvalidElfFormat);
}
let written = unsafe { syscalls::write(fd as _, bytes.as_ptr().cast_mut(), bytes.len()) };
if written != bytes.len() as _ {
unsafe { syscalls::close(fd as i32) };
return Err(RunError::BytesNotWritten(written as usize, bytes.len()));
}
Ok(())
}
fn prepare_argv(
fd: u16,
options: &RunOptions<'_>,
storage: &mut [[u8; MAX_STRING_LEN]; MAX_ARGS],
ptrs: &mut [*const u8; MAX_ARGS + 1],
) -> Result<usize, RunError> {
if let Some(custom_argv0) = options.argv0 {
let argv0_bytes = custom_argv0.as_bytes();
if argv0_bytes.len() >= MAX_STRING_LEN {
return Err(RunError::ArgTooLong);
}
storage[0][..argv0_bytes.len()].copy_from_slice(argv0_bytes);
storage[0][argv0_bytes.len()] = 0; } else {
let path = build_path(fd);
let null_pos = path.iter().position(|&b| b == 0).unwrap();
storage[0][..null_pos].copy_from_slice(&path[..null_pos]);
storage[0][null_pos] = 0; }
let mut arg_count = 1;
if let Some(user_args) = options.args {
if user_args.len() > MAX_ARGS - 1 {
return Err(RunError::TooManyArgs);
}
for &arg in user_args.iter() {
let arg_bytes = arg.as_bytes();
if arg_bytes.len() >= MAX_STRING_LEN {
return Err(RunError::ArgTooLong);
}
storage[arg_count][..arg_bytes.len()].copy_from_slice(arg_bytes);
storage[arg_count][arg_bytes.len()] = 0; arg_count += 1;
}
}
for i in 0..arg_count {
ptrs[i] = storage[i].as_ptr();
}
ptrs[arg_count] = core::ptr::null();
Ok(arg_count)
}
fn prepare_envp(
env: Option<&[&str]>,
storage: &mut [[u8; MAX_STRING_LEN]; MAX_ENV],
ptrs: &mut [*const u8; MAX_ENV + 1],
) -> Result<usize, RunError> {
let mut env_count = 0;
if let Some(user_env) = env {
if user_env.len() > MAX_ENV {
return Err(RunError::TooManyEnvVars);
}
for (i, &env_var) in user_env.iter().enumerate() {
let env_bytes = env_var.as_bytes();
if env_bytes.len() >= MAX_STRING_LEN {
return Err(RunError::EnvVarTooLong);
}
storage[i][..env_bytes.len()].copy_from_slice(env_bytes);
storage[i][env_bytes.len()] = 0; ptrs[i] = storage[i].as_ptr();
env_count += 1;
}
}
ptrs[env_count] = core::ptr::null();
Ok(env_count)
}
fn execute_child(fd: u16, options: &RunOptions<'_>) -> Result<i32, RunError> {
let path = build_path(fd);
let mut argv_storage: [[u8; MAX_STRING_LEN]; MAX_ARGS] = [[0; MAX_STRING_LEN]; MAX_ARGS];
let mut argv: [*const u8; MAX_ARGS + 1] = [core::ptr::null(); MAX_ARGS + 1];
let mut envp_storage: [[u8; MAX_STRING_LEN]; MAX_ENV] = [[0; MAX_STRING_LEN]; MAX_ENV];
let mut envp: [*const u8; MAX_ENV + 1] = [core::ptr::null(); MAX_ENV + 1];
prepare_argv(fd, options, &mut argv_storage, &mut argv)?;
prepare_envp(options.env, &mut envp_storage, &mut envp)?;
let ret = unsafe { syscalls::execve(path, argv.as_ptr() as *mut u8, envp.as_ptr() as *mut u8) };
if ret == -1 {
return Err(RunError::ExecError(-1)); }
unreachable!("execve should not return on success");
}
fn execute(fd: u16, options: RunOptions<'_>) -> Result<i32, RunError> {
let pid = match options.replace {
true => 0, false => unsafe { syscalls::fork() },
};
match pid {
0 => execute_child(fd, &options),
-1 => Err(RunError::ForkError(-1)), _ => {
let mut status: i32 = 0;
let waited_pid = unsafe {
syscalls::wait4(
pid,
&mut status as *mut i32 as *mut u8,
0,
core::ptr::null_mut(),
)
};
if waited_pid == -1 {
return Err(RunError::WaitError(-1)); }
Ok((status >> 8) & 0xff)
}
}
}
const EXEC_PATH: [u8; 20] = *b"/proc/self/fd/\0\0\0\0\0\0";
const EXEC_PATH_LEN: usize = EXEC_PATH.len();
fn build_path(fd: u16) -> [u8; EXEC_PATH_LEN] {
let mut path = [0u8; EXEC_PATH_LEN];
unsafe { core::ptr::copy_nonoverlapping(EXEC_PATH.as_ptr(), path.as_mut_ptr(), EXEC_PATH_LEN) };
let mut idx = 14;
if fd >= 10000 {
path[idx] = b'0' + (fd / 10000) as u8;
idx += 1;
}
if fd >= 1000 {
path[idx] = b'0' + ((fd / 1000) % 10) as u8;
idx += 1;
}
if fd >= 100 {
path[idx] = b'0' + ((fd / 100) % 10) as u8;
idx += 1;
}
if fd >= 10 {
path[idx] = b'0' + ((fd / 10) % 10) as u8;
idx += 1;
}
path[idx] = b'0' + (fd % 10) as u8;
path
}
#[cfg(test)]
mod tests {
use super::*;
extern crate std;
use std::format;
fn test_prepare_argv(fd: u16, options: &RunOptions<'_>) -> Result<usize, RunError> {
let mut storage: [[u8; MAX_STRING_LEN]; MAX_ARGS] = [[0; MAX_STRING_LEN]; MAX_ARGS];
let mut ptrs: [*const u8; MAX_ARGS + 1] = [core::ptr::null(); MAX_ARGS + 1];
prepare_argv(fd, options, &mut storage, &mut ptrs)
}
fn test_prepare_envp(env: Option<&[&str]>) -> Result<usize, RunError> {
let mut storage: [[u8; MAX_STRING_LEN]; MAX_ENV] = [[0; MAX_STRING_LEN]; MAX_ENV];
let mut ptrs: [*const u8; MAX_ENV + 1] = [core::ptr::null(); MAX_ENV + 1];
prepare_envp(env, &mut storage, &mut ptrs)
}
#[test]
fn test_run_options_default() {
let options = RunOptions::new();
assert!(!options.replace);
assert!(options.args.is_none());
assert!(options.env.is_none());
}
#[test]
fn test_run_options_with_replace() {
let options = RunOptions::new().with_replace(true);
assert!(options.replace);
}
#[test]
fn test_run_options_with_args() {
let args = ["test", "arg1", "arg2"];
let options = RunOptions::new().with_args(&args);
assert!(options.args.is_some());
assert_eq!(options.args.unwrap().len(), 3);
assert_eq!(options.args.unwrap()[0], "test");
assert_eq!(options.args.unwrap()[1], "arg1");
assert_eq!(options.args.unwrap()[2], "arg2");
}
#[test]
fn test_run_options_with_env() {
let env = ["PATH=/usr/bin", "HOME=/tmp"];
let options = RunOptions::new().with_env(&env);
assert!(options.env.is_some());
assert_eq!(options.env.unwrap().len(), 2);
assert_eq!(options.env.unwrap()[0], "PATH=/usr/bin");
assert_eq!(options.env.unwrap()[1], "HOME=/tmp");
}
#[test]
fn test_run_options_chaining() {
let args = ["test", "arg1"];
let env = ["VAR=value"];
let options = RunOptions::new()
.with_replace(true)
.with_args(&args)
.with_env(&env);
assert!(options.replace);
assert!(options.args.is_some());
assert!(options.env.is_some());
assert_eq!(options.args.unwrap().len(), 2);
assert_eq!(options.env.unwrap().len(), 1);
}
#[test]
fn test_run_options_with_argv0() {
let options = RunOptions::new().with_argv0("custom-program");
assert!(options.argv0.is_some());
assert_eq!(options.argv0.unwrap(), "custom-program");
}
#[test]
fn test_run_options_argv0_chaining() {
let args = ["test", "arg1"];
let env = ["VAR=value"];
let options = RunOptions::new()
.with_argv0("my-program")
.with_args(&args)
.with_env(&env);
assert!(options.argv0.is_some());
assert_eq!(options.argv0.unwrap(), "my-program");
assert!(options.args.is_some());
assert!(options.env.is_some());
}
#[test]
fn test_prepare_argv_path_only() {
let options = RunOptions::new();
let result = test_prepare_argv(123, &options);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 1);
}
#[test]
fn test_prepare_argv_with_args() {
let args = ["arg1", "arg2"];
let options = RunOptions::new().with_args(&args);
let result = test_prepare_argv(123, &options);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 3);
}
#[test]
fn test_prepare_argv_too_many_args() {
let mut args = std::vec::Vec::new();
for _i in 0..MAX_ARGS {
args.push("arg");
}
let options = RunOptions::new().with_args(&args);
let result = test_prepare_argv(123, &options);
assert!(matches!(result, Err(RunError::TooManyArgs)));
}
#[test]
fn test_prepare_argv_arg_too_long() {
let long_arg = "a".repeat(MAX_STRING_LEN);
let args = [long_arg.as_str()];
let options = RunOptions::new().with_args(&args);
let result = test_prepare_argv(123, &options);
assert!(matches!(result, Err(RunError::ArgTooLong)));
}
#[test]
fn test_prepare_argv_custom_argv0() {
let options = RunOptions::new().with_argv0("my-custom-program");
let result = test_prepare_argv(123, &options);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 1);
}
#[test]
fn test_prepare_argv_custom_argv0_with_args() {
let args = ["arg1", "arg2"];
let options = RunOptions::new().with_argv0("custom-name").with_args(&args);
let result = test_prepare_argv(123, &options);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 3);
}
#[test]
fn test_prepare_argv_custom_argv0_too_long() {
let long_argv0 = "a".repeat(MAX_STRING_LEN);
let options = RunOptions::new().with_argv0(&long_argv0);
let result = test_prepare_argv(123, &options);
assert!(matches!(result, Err(RunError::ArgTooLong)));
}
#[test]
fn test_prepare_envp_none() {
let result = test_prepare_envp(None);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_prepare_envp_with_env() {
let env = ["PATH=/usr/bin", "HOME=/tmp"];
let result = test_prepare_envp(Some(&env));
assert!(result.is_ok());
assert_eq!(result.unwrap(), 2);
}
#[test]
fn test_prepare_envp_too_many_vars() {
let mut env = std::vec::Vec::new();
for _i in 0..MAX_ENV + 1 {
env.push("VAR=value");
}
let result = test_prepare_envp(Some(&env));
assert!(matches!(result, Err(RunError::TooManyEnvVars)));
}
#[test]
fn test_prepare_envp_var_too_long() {
let long_var = format!("VAR={}", "a".repeat(MAX_STRING_LEN));
let env = [long_var.as_str()];
let result = test_prepare_envp(Some(&env));
assert!(matches!(result, Err(RunError::EnvVarTooLong)));
}
#[test]
fn test_new_error_types() {
let errors = [
RunError::TooManyArgs,
RunError::TooManyEnvVars,
RunError::ArgTooLong,
RunError::EnvVarTooLong,
];
for error in &errors {
let debug_str = format!("{error:?}");
assert!(!debug_str.is_empty());
}
}
}