diff --git a/reverie-ptrace/Cargo.toml b/reverie-ptrace/Cargo.toml index 69f6763..cffad59 100644 --- a/reverie-ptrace/Cargo.toml +++ b/reverie-ptrace/Cargo.toml @@ -24,7 +24,7 @@ perf-event-open-sys = "4.0" procfs = "0.9" raw-cpuid = "10.6.0" reverie = { version = "0.1.0", path = "../reverie" } -safeptrace = { version = "0.1.0", path = "../safeptrace" } +safeptrace = { version = "0.1.0", path = "../safeptrace", features = ["memory", "notifier"] } serde = { version = "1.0.136", features = ["derive", "rc"] } thiserror = "1.0.36" tokio = { version = "1.25.0", features = ["full", "test-util", "tracing"] } diff --git a/safeptrace/Cargo.toml b/safeptrace/Cargo.toml index 1730213..e134c85 100644 --- a/safeptrace/Cargo.toml +++ b/safeptrace/Cargo.toml @@ -14,7 +14,7 @@ lazy_static = "1.4" libc = "0.2.139" nix = "0.25" parking_lot = { version = "0.11.2", features = ["send_guard"] } -reverie-memory = { version = "0.1.0", path = "../reverie-memory" } +reverie-memory = { version = "0.1.0", path = "../reverie-memory", optional = true } reverie-process = { version = "0.1.0", path = "../reverie-process" } syscalls = { version = "0.6.7", features = ["serde"] } thiserror = "1.0.36" @@ -23,3 +23,8 @@ thiserror = "1.0.36" quickcheck = "1.0" quickcheck_macros = "1.0" tokio = { version = "1.25.0", features = ["full", "test-util", "tracing"] } + +[features] +default = [] +memory = ["reverie-memory"] +notifier = [] diff --git a/safeptrace/README.md b/safeptrace/README.md new file mode 100644 index 0000000..74bdb34 --- /dev/null +++ b/safeptrace/README.md @@ -0,0 +1,45 @@ +# A safe ptrace interface + +This crate provides a safe and Rustic alternative to the infamous `ptrace` API. +There are many extremely subtle aspects of the raw ptrace API and this crate +helps avoid common pitfalls. + +Note that this library is still rather low-level and does not claim to solve all +your ptrace problems. You have been warned! + +## Features + + * Ergonomic interface that provides a state machine for ptrace states. This + avoids the infamous `ESRCH` errors that can happen when you use the ptrace + API incorrectly. + * Provides an interface to read/write guest's memory (see "memory" feature + flag). + * Provides an optional and semi-experimental async interface, which can be used + with `tokio` (see "notifier" feature). + +## Usage + +Add this to your `Cargo.toml` file: +``` +safeptrace = "0.1" +``` + +## Feature Flags + +### `"memory"` (off by default) + +Provides access to the guest's memory. Memory can only be safely accessed when +the guest is in a stopped state, thus the `MemoryAccess` trait is only +implemented for the `Stopped` type. + +### `"notifier"` (off by default) + +Provides an async interface for ptrace using notifier threads. This is +semi-experimental, but testing shows that it has very good performance. It works +by spawning a separate thread for each thread being traced, waiting for ptrace +events in a loop. Thus, there will be 1 thread per guest thread. + +Use with: +``` +safeptrace = { version = "0.1", features = ["async"] } +``` diff --git a/safeptrace/src/lib.rs b/safeptrace/src/lib.rs index fffe351..c6c6902 100644 --- a/safeptrace/src/lib.rs +++ b/safeptrace/src/lib.rs @@ -12,8 +12,9 @@ //! A safe ptrace API. This API forces correct usage of ptrace in that it is //! not possible to call ptrace on a process not in a stopped state. +#[cfg(feature = "memory")] mod memory; -#[allow(unused)] +#[cfg(feature = "notifier")] mod notifier; mod regs; mod waitid; @@ -473,11 +474,12 @@ impl Stopped { self.map_err(Errno::new(err as i32)) } - /// Waits for the next exit stop to occur. This is received asynchronously - /// regardless of what the process was doing at the time. This is useful for - /// canceling futures when a process enters a `PTRACE_EVENT_EXIT` (such as - /// when one thread calls `exit_group` and causes all other threads to - /// suddenly exit). + /// Returns a future that is notified when the next exit stop occurs. This + /// is received asynchronously regardless of what the process was doing at + /// the time. This is useful for canceling futures when a process enters a + /// `PTRACE_EVENT_EXIT` (such as when one thread calls `exit_group` and + /// causes all other threads to suddenly exit). + #[cfg(feature = "notifier")] pub fn exit_event(&self) -> notifier::ExitFuture { notifier::ExitFuture(self.0) } @@ -489,7 +491,6 @@ impl Stopped { /// pid really is in a stopped state. It is better to arrive at a stopped /// state via other methods such as `Running::wait`. pub fn new_unchecked(pid: Pid) -> Self { - // FIXME: Remove this method. Stopped(pid) } @@ -860,6 +861,7 @@ impl Running { /// Like `wait`, but filters out events we don't care about by resuming the /// tracee when encountering them. This is useful for skipping past spurious /// events until a point we know the tracee must stop. + #[cfg(feature = "notifier")] pub async fn wait_until(mut self, mut pred: F) -> Result where F: FnMut(&Event) -> bool, @@ -882,6 +884,7 @@ impl Running { /// Waits until we receive a specific stop signal. Useful for skipping past /// spurious signals. + #[cfg(feature = "notifier")] pub async fn wait_for_signal(self, sig: Signal) -> Result { self.wait_until(|event| event == &Event::Signal(sig)).await } @@ -891,11 +894,18 @@ impl Running { /// canceling futures when a process enters a `PTRACE_EVENT_EXIT` (such as /// when one thread calls `exit_group` and causes all other threads to /// suddenly exit). + #[cfg(feature = "notifier")] pub fn exit_event(&self) -> notifier::ExitFuture { notifier::ExitFuture(self.0) } /// Like `wait`, but wait asynchronously for the next state change. + /// + /// NOTE: This call should not be mixed with [`Running::wait`]!! Once + /// [`Running::next_state`] is called once, [`Running::wait`] should never + /// be called again for that PID. This is because a notifier thread takes + /// over and calls `wait` in a continuous loop. + #[cfg(feature = "notifier")] pub async fn next_state(self) -> Result { notifier::WaitFuture(self).await } @@ -918,6 +928,7 @@ impl Zombie { } /// Reaps the zombie by waiting for it to fully exit. + #[cfg(feature = "notifier")] pub async fn reap(self) -> ExitStatus { // The tracee may not be fully dead yet. It is still possible for it to // still enter an `Event::Exit` state by waiting on it. For more info, @@ -1260,6 +1271,7 @@ mod test { Ok(()) } + #[cfg(feature = "notifier")] #[cfg(not(sanitized))] #[tokio::test] async fn notifier_basic() -> Result<(), Box> {