use std::fs::File; use std::io::Write; use std::process::Command; use tempfile::{TempDir, NamedTempFile}; #[rustfmt::skip] const STATIC_ARGS: &[&str] = &[ "--ro-bind", "/usr", "/usr", "--dir", "/tmp", "--dir", "/var", "--symlink", "../tmp", "/var/tmp", "--proc", "/proc", "--dev", "/dev", "--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf", "--symlink", "usr/lib", "/lib", "--symlink", "usr/lib64", "/lib64", "--symlink", "usr/bin", "/bin", "--symlink", "usr/sbin", "/sbin", "--unshare-all", "--die-with-parent", "--clearenv", "--setenv", "PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "--setenv", "HAREPATH", "/usr/local/src/hare/stdlib", ]; const HARE_COMMAND: &str = "/usr/local/bin/hare"; /// Default command timeout in seconds. const DEFAULT_TIMEOUT: u32 = 30; fn passwd_files(uid: u32, gid: u32) -> (NamedTempFile, NamedTempFile, Vec) { let uid = uid.to_string(); let passwd = Command::new("getent") .args(["passwd", &uid, "65534"]) .output() .expect("failed to run getent passwd"); let gid = gid.to_string(); let group = Command::new("getent") .args(["group", &gid, "65534"]) .output() .expect("failed to run getent passwd"); let mut passwd_file = tempfile::NamedTempFile::new().unwrap(); passwd_file.write_all(&passwd.stdout).unwrap(); passwd_file.flush().unwrap(); let mut group_file = tempfile::NamedTempFile::new().unwrap(); group_file.write_all(&group.stdout).unwrap(); group_file.flush().unwrap(); let args = vec![ "--ro-bind".to_string(), passwd_file.path().display().to_string(), "/etc/passwd".to_string(), "--ro-bind".to_string(), group_file.path().display().to_string(), "/etc/group".to_string(), ]; (passwd_file, group_file, args) } fn home_dir(uid: u32) -> Vec { let user_dir = format!("/run/user/{uid}"); let home_dir = format!("{user_dir}/home"); vec![ "--dir".to_string(), home_dir.clone(), "--setenv".to_string(), "HOME".to_string(), home_dir, "--setenv".to_string(), "XDG_RUNTIME_DIR".to_string(), user_dir, ] } fn shared_app_dir() -> (TempDir, Vec) { let tmp_app_dir = tempfile::tempdir().unwrap(); let app_dir_path = tmp_app_dir.path().display().to_string(); ( tmp_app_dir, vec![ "--bind".to_string(), app_dir_path, "/app".to_string(), "--chdir".to_string(), "/app".to_string(), ], ) } fn command(app_file: &str) -> Vec { vec![ "/usr/bin/timeout".to_string(), DEFAULT_TIMEOUT.to_string(), HARE_COMMAND.to_string(), "run".to_string(), app_file.to_string(), ] } fn getids() -> (u32, u32) { unsafe { (libc::getuid(), libc::getgid()) } } pub fn run_code(code: &str) -> std::process::Output { let (uid, gid) = getids(); let mut bwrap_cmd = Command::new("bwrap"); bwrap_cmd.args(STATIC_ARGS); let (app_dir, dir_arg) = shared_app_dir(); bwrap_cmd.args(dir_arg); let home_args = home_dir(uid); bwrap_cmd.args(home_args); let (_passwd, _group, file_args) = passwd_files(uid, gid); bwrap_cmd.args(file_args); { let code_path = app_dir.path().join("main.ha"); let mut code_file = File::create(code_path).unwrap(); code_file.write_all(code.as_bytes()).unwrap(); } let cmd_args = command("/app/main.ha"); bwrap_cmd.args(cmd_args); let output = bwrap_cmd.output().expect("running bwrap failed"); output }