wip: proper signal handling
This commit is contained in:
parent
0bb5e7b021
commit
0b34ee2394
69
Cargo.lock
generated
69
Cargo.lock
generated
@ -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"
|
||||
|
@ -10,5 +10,6 @@ log = "0.4"
|
||||
env_logger = "0.9"
|
||||
thiserror = "1"
|
||||
async-std = "1"
|
||||
futures = "0.3.17"
|
||||
|
||||
nix = "0.23.1"
|
||||
|
@ -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),
|
||||
}
|
||||
|
199
src/main.rs
199
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<Pid>,
|
||||
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
@ -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<Child, Error> {
|
||||
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<Never, Error> {
|
||||
// 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::<Vec<_>>()
|
||||
.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<Mutex<HashMap<Pid, ProcessDescription>>>) -> 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<Event> {
|
||||
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<Pid, ProcessDescription>) {}
|
||||
|
Loading…
Reference in New Issue
Block a user