diff --git a/Cargo.lock b/Cargo.lock index dd424ac..9ede742 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,11 +252,27 @@ version = "0.1.0" dependencies = [ "async-std", "env_logger", + "futures", "log", "nix", "thiserror", ] +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.21" @@ -264,6 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -272,6 +289,17 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.21" @@ -293,6 +321,47 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gloo-timers" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index cd5e41f..f022843 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,6 @@ log = "0.4" env_logger = "0.9" thiserror = "1" async-std = "1" +futures = "0.3.17" nix = "0.23.1" diff --git a/src/error.rs b/src/error.rs index 83b5e76..78fdd62 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,12 @@ +use async_std::io; + use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("{msg}: {src}")] Nix { msg: &'static str, src: nix::Error }, + + #[error("io: {0}")] + Io(#[from] io::Error), } diff --git a/src/main.rs b/src/main.rs index 442aceb..e0e7851 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use log::{error, info}; +use log::{debug, error, info, warn}; mod error; @@ -6,19 +6,33 @@ use error::Error; use std::collections::HashMap; use std::ffi::{CStr, CString}; +use std::os::unix::io::{FromRawFd, RawFd}; use std::sync::Arc; +use async_std::fs::File; +use async_std::io::{self, ReadExt, WriteExt}; use async_std::sync::Mutex; use async_std::task; +use futures::try_join; -use nix::sys::ptrace::{self, Options}; +use nix::sys::ptrace::{self, Event, Options}; use nix::sys::signal::Signal; use nix::sys::wait::{wait, WaitStatus}; use nix::unistd::Pid; use nix::unistd::{self, ForkResult}; +struct Child { + pid: Pid, + + stdin: File, + stdout: File, + stderr: File, +} + struct ProcessDescription { + state: ProcessState, parent: Option, + name: Option, } @@ -28,33 +42,117 @@ enum ProcessState { } fn main() -> Result<(), Error> { - env_logger::init(); + let env = env_logger::Env::new().filter_or("LOG", "info"); + env_logger::init_from_env(env); info!("forking child process..."); - let child = unsafe { + let child = spawn_child()?; + task::block_on(async_main(child)) +} + +fn spawn_child() -> Result { + let child_stdin = unistd::pipe().map_err(|e| Error::Nix { + msg: "pipe", + src: e, + })?; + + let child_stdout = unistd::pipe().map_err(|e| Error::Nix { + msg: "pipe", + src: e, + })?; + + let child_stderr = unistd::pipe().map_err(|e| Error::Nix { + msg: "pipe", + src: e, + })?; + + let pid = unsafe { match nix::unistd::fork().map_err(|e| Error::Nix { msg: "fork", src: e, })? { ForkResult::Parent { child } => child, ForkResult::Child => { - let err = spawn_child(); + let err = run_child( + vec!["/bin/sh", "-c", "/bin/true"], + child_stdin.0, + child_stdout.1, + child_stderr.1, + ); error!("error spawning child: {}", err); std::process::exit(1); } } }; - task::block_on(async_main(child)) + Ok(Child { + pid, + stdin: unsafe { File::from_raw_fd(child_stdin.1) }, + stdout: unsafe { File::from_raw_fd(child_stdout.0) }, + stderr: unsafe { File::from_raw_fd(child_stderr.0) }, + }) } -async fn async_main(child: Pid) -> Result<(), Error> { +fn run_child(args: Vec<&str>, stdin: RawFd, stdout: RawFd, stderr: RawFd) -> Error { + enum Never {} + + fn int(args: Vec<&str>, stdin: RawFd, stdout: RawFd, stderr: RawFd) -> Result { + // swap 3 main fds + unistd::dup2(stdin, 0).map_err(|e| Error::Nix { + msg: "dup2", + src: e, + })?; + unistd::dup2(stdout, 1).map_err(|e| Error::Nix { + msg: "dup2", + src: e, + })?; + unistd::dup2(stderr, 2).map_err(|e| Error::Nix { + msg: "dup2", + src: e, + })?; + + // ask to be traced (stops process) + ptrace::traceme().map_err(|e| Error::Nix { + msg: "PTRACE_TRACEME", + src: e, + })?; + + unistd::execve::<_, &CStr>( + &CString::new(args[0]).unwrap(), + args.into_iter() + .map(CString::new) + .map(Result::unwrap) + .collect::>() + .as_slice(), + &[], + ) + .map_err(|e| Error::Nix { + msg: "execve", + src: e, + })?; + + unreachable!("execve doesn't return on success") + } + + match int(args, stdin, stdout, stderr) { + Ok(s) => match s {}, + Err(e) => e, + } +} + +async fn async_main(child: Child) -> Result<(), Error> { + let child_stdin = child.stdin; + let child_stdout = child.stdout; + let child_stderr = child.stderr; + let child = child.pid; + let children = Arc::new(Mutex::new({ let mut m = HashMap::new(); m.insert( child, ProcessDescription { + state: ProcessState::Traced, parent: None, name: Some("main".to_string()), }, @@ -62,6 +160,8 @@ async fn async_main(child: Pid) -> Result<(), Error> { m })); + let wait_loop = task::spawn(wait_loop(children.clone())); + ptrace::setoptions( child, Options::PTRACE_O_TRACECLONE @@ -70,10 +170,37 @@ async fn async_main(child: Pid) -> Result<(), Error> { | Options::PTRACE_O_TRACEVFORK, ) .map_err(|e| Error::Nix { - msg: "ptrace_setoptions", + msg: "PTRACE_SETOPTIONS", src: e, })?; + ptrace::cont(child, None).map_err(|e| Error::Nix { + msg: "PTRACE_CONT", + src: e, + })?; + + try_join!(wait_loop, task::spawn(handle_input(child_stdin)))?; + Ok(()) +} + +async fn handle_input(mut stdin_pipe: File) -> Result<(), Error> { + let mut stdin = io::stdin(); + + let mut bytes_in = vec![0 as u8; 128]; + let mut bytes_out = vec![0 as u8; 128]; + loop { + let num_read = stdin.read(&mut bytes_in).await?; + + for i in 0..num_read { + debug!("forwarded byte: {}", i); + bytes_out[i] = bytes_in[i]; + } + + stdin_pipe.write_all(&bytes_out[0..num_read]).await?; + } +} + +async fn wait_loop(children: Arc>>) -> Result<(), Error> { while !children.lock().await.is_empty() { let wait_result = wait().map_err(|e| Error::Nix { msg: "wait", @@ -81,37 +208,43 @@ async fn async_main(child: Pid) -> Result<(), Error> { })?; match &wait_result { - WaitStatus::Signaled(pid, Signal::SIGTRAP, _) => { - ptrace::cont(*pid, None).map_err(|e| Error::Nix { - msg: "ptrace_cont", - src: e, - })?; + WaitStatus::PtraceEvent(pid, _, event) => match match_event(*event) { + Some(Event::PTRACE_EVENT_FORK) => info!("process {} forked", pid), + Some(Event::PTRACE_EVENT_VFORK) => info!("process {} vforked", pid), + Some(Event::PTRACE_EVENT_CLONE) => info!("process {} cloned", pid), + Some(Event::PTRACE_EVENT_EXEC) => info!("process {} execed", pid), + Some(Event::PTRACE_EVENT_VFORK_DONE) => { + info!("process {} returned from vfork", pid) + } + Some(Event::PTRACE_EVENT_EXIT) => info!("process {} exited", pid), + Some(Event::PTRACE_EVENT_SECCOMP) => { + info!("process {} triggered a seccomp rule", pid) + } + Some(Event::PTRACE_EVENT_STOP) => info!("process {} stopped", pid), + + Some(e) => warn!("new ptrace event added: {:?}", e), + None => warn!("unrecognised ptrace event: {}", event), + }, + WaitStatus::Stopped(pid, signal) => { + info!("process {} stopped with signal {}", pid, signal) } - _ => unimplemented!(), + s => warn!("unhandled signal: {:?}", s), }; } Ok(()) } -fn spawn_child() -> Error { - fn int() -> Result<(), Error> { - ptrace::traceme().map_err(|e| Error::Nix { - msg: "traceme", - src: e, - })?; - - let sh = CString::new("/bin/sh").unwrap(); - - unistd::execve::<_, &CStr>(&sh, vec![&sh].as_slice(), &[]).map_err(|e| Error::Nix { - msg: "execve", - src: e, - })?; - - unreachable!() +fn match_event(v: i32) -> Option { + match v { + 1 => Some(Event::PTRACE_EVENT_FORK), + 2 => Some(Event::PTRACE_EVENT_VFORK), + 3 => Some(Event::PTRACE_EVENT_CLONE), + 4 => Some(Event::PTRACE_EVENT_EXEC), + 5 => Some(Event::PTRACE_EVENT_VFORK_DONE), + 6 => Some(Event::PTRACE_EVENT_EXIT), + 7 => Some(Event::PTRACE_EVENT_SECCOMP), + 128 => Some(Event::PTRACE_EVENT_STOP), + _ => None, } - - int().expect_err("execve doesn't return on success") } - -async fn wait_loop(desc: &mut HashMap) {}