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 = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"nix",
|
"nix",
|
||||||
"thiserror",
|
"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]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
@ -264,6 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -272,6 +289,17 @@ version = "0.3.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
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]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
@ -293,6 +321,47 @@ dependencies = [
|
|||||||
"waker-fn",
|
"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]]
|
[[package]]
|
||||||
name = "gloo-timers"
|
name = "gloo-timers"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -10,5 +10,6 @@ log = "0.4"
|
|||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
async-std = "1"
|
async-std = "1"
|
||||||
|
futures = "0.3.17"
|
||||||
|
|
||||||
nix = "0.23.1"
|
nix = "0.23.1"
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
|
use async_std::io;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("{msg}: {src}")]
|
#[error("{msg}: {src}")]
|
||||||
Nix { msg: &'static str, src: nix::Error },
|
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;
|
mod error;
|
||||||
|
|
||||||
@ -6,19 +6,33 @@ use error::Error;
|
|||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::os::unix::io::{FromRawFd, RawFd};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_std::fs::File;
|
||||||
|
use async_std::io::{self, ReadExt, WriteExt};
|
||||||
use async_std::sync::Mutex;
|
use async_std::sync::Mutex;
|
||||||
use async_std::task;
|
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::signal::Signal;
|
||||||
use nix::sys::wait::{wait, WaitStatus};
|
use nix::sys::wait::{wait, WaitStatus};
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
use nix::unistd::{self, ForkResult};
|
use nix::unistd::{self, ForkResult};
|
||||||
|
|
||||||
|
struct Child {
|
||||||
|
pid: Pid,
|
||||||
|
|
||||||
|
stdin: File,
|
||||||
|
stdout: File,
|
||||||
|
stderr: File,
|
||||||
|
}
|
||||||
|
|
||||||
struct ProcessDescription {
|
struct ProcessDescription {
|
||||||
|
state: ProcessState,
|
||||||
parent: Option<Pid>,
|
parent: Option<Pid>,
|
||||||
|
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,33 +42,117 @@ enum ProcessState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
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...");
|
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 {
|
match nix::unistd::fork().map_err(|e| Error::Nix {
|
||||||
msg: "fork",
|
msg: "fork",
|
||||||
src: e,
|
src: e,
|
||||||
})? {
|
})? {
|
||||||
ForkResult::Parent { child } => child,
|
ForkResult::Parent { child } => child,
|
||||||
ForkResult::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);
|
error!("error spawning child: {}", err);
|
||||||
std::process::exit(1);
|
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 children = Arc::new(Mutex::new({
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
m.insert(
|
m.insert(
|
||||||
child,
|
child,
|
||||||
ProcessDescription {
|
ProcessDescription {
|
||||||
|
state: ProcessState::Traced,
|
||||||
parent: None,
|
parent: None,
|
||||||
name: Some("main".to_string()),
|
name: Some("main".to_string()),
|
||||||
},
|
},
|
||||||
@ -62,6 +160,8 @@ async fn async_main(child: Pid) -> Result<(), Error> {
|
|||||||
m
|
m
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let wait_loop = task::spawn(wait_loop(children.clone()));
|
||||||
|
|
||||||
ptrace::setoptions(
|
ptrace::setoptions(
|
||||||
child,
|
child,
|
||||||
Options::PTRACE_O_TRACECLONE
|
Options::PTRACE_O_TRACECLONE
|
||||||
@ -70,10 +170,37 @@ async fn async_main(child: Pid) -> Result<(), Error> {
|
|||||||
| Options::PTRACE_O_TRACEVFORK,
|
| Options::PTRACE_O_TRACEVFORK,
|
||||||
)
|
)
|
||||||
.map_err(|e| Error::Nix {
|
.map_err(|e| Error::Nix {
|
||||||
msg: "ptrace_setoptions",
|
msg: "PTRACE_SETOPTIONS",
|
||||||
src: e,
|
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() {
|
while !children.lock().await.is_empty() {
|
||||||
let wait_result = wait().map_err(|e| Error::Nix {
|
let wait_result = wait().map_err(|e| Error::Nix {
|
||||||
msg: "wait",
|
msg: "wait",
|
||||||
@ -81,37 +208,43 @@ async fn async_main(child: Pid) -> Result<(), Error> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
match &wait_result {
|
match &wait_result {
|
||||||
WaitStatus::Signaled(pid, Signal::SIGTRAP, _) => {
|
WaitStatus::PtraceEvent(pid, _, event) => match match_event(*event) {
|
||||||
ptrace::cont(*pid, None).map_err(|e| Error::Nix {
|
Some(Event::PTRACE_EVENT_FORK) => info!("process {} forked", pid),
|
||||||
msg: "ptrace_cont",
|
Some(Event::PTRACE_EVENT_VFORK) => info!("process {} vforked", pid),
|
||||||
src: e,
|
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)
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
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)
|
||||||
|
}
|
||||||
|
s => warn!("unhandled signal: {:?}", s),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_child() -> Error {
|
fn match_event(v: i32) -> Option<Event> {
|
||||||
fn int() -> Result<(), Error> {
|
match v {
|
||||||
ptrace::traceme().map_err(|e| Error::Nix {
|
1 => Some(Event::PTRACE_EVENT_FORK),
|
||||||
msg: "traceme",
|
2 => Some(Event::PTRACE_EVENT_VFORK),
|
||||||
src: e,
|
3 => Some(Event::PTRACE_EVENT_CLONE),
|
||||||
})?;
|
4 => Some(Event::PTRACE_EVENT_EXEC),
|
||||||
|
5 => Some(Event::PTRACE_EVENT_VFORK_DONE),
|
||||||
let sh = CString::new("/bin/sh").unwrap();
|
6 => Some(Event::PTRACE_EVENT_EXIT),
|
||||||
|
7 => Some(Event::PTRACE_EVENT_SECCOMP),
|
||||||
unistd::execve::<_, &CStr>(&sh, vec![&sh].as_slice(), &[]).map_err(|e| Error::Nix {
|
128 => Some(Event::PTRACE_EVENT_STOP),
|
||||||
msg: "execve",
|
_ => None,
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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