diff --git a/Cargo.lock b/Cargo.lock index f0ff75fbe..594f1ca07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1372,6 +1372,7 @@ dependencies = [ name = "fspy_preload_unix" version = "0.0.0" dependencies = [ + "allocator-api2", "ctor", "fspy_client_unix", "fspy_nostd", diff --git a/crates/fspy_client_unix/src/lib.rs b/crates/fspy_client_unix/src/lib.rs index 2165a00cf..a0a7d58ce 100644 --- a/crates/fspy_client_unix/src/lib.rs +++ b/crates/fspy_client_unix/src/lib.rs @@ -103,6 +103,7 @@ impl<'a> Client<'a> { &self, config: ExecResolveConfig, raw_exec: RawExec, + allocator: impl Allocator, f: impl FnOnce(RawExec, Option) -> nix::Result, ) -> nix::Result { // SAFETY: raw_exec contains valid pointers to C strings and @@ -111,7 +112,7 @@ impl<'a> Client<'a> { let pre_exec = handle_exec(&mut exec, config, &self.encoded_payload, |mode, path| { self.send(mode, path); })?; - RawExec::from_exec(exec, |raw_command| f(raw_command, pre_exec)) + RawExec::from_exec(exec, allocator, |raw_command| f(raw_command, pre_exec)) } /// Resolves and reports one intercepted file access. @@ -128,12 +129,12 @@ impl<'a> Client<'a> { &self, path: impl ToAbsolutePath, mode: impl ToAccessMode, + allocator: impl Allocator, ) -> anyhow::Result<()> { // SAFETY: mode contains a valid pointer (if ModeStr) or a plain value, // as provided by the caller. let mode = unsafe { mode.to_access_mode() }; - let arena = fspy_nostd_alloc::pooled_bump(); - let Some(abs_path) = path.to_absolute_path(&arena)? else { + let Some(abs_path) = path.to_absolute_path(&allocator)? else { return Ok(()); }; self.send(mode, Path::new(OsStr::from_bytes(abs_path.as_units()))); diff --git a/crates/fspy_client_unix/src/raw_exec.rs b/crates/fspy_client_unix/src/raw_exec.rs index e7e6c4cf8..9ef4fb3bc 100644 --- a/crates/fspy_client_unix/src/raw_exec.rs +++ b/crates/fspy_client_unix/src/raw_exec.rs @@ -1,5 +1,6 @@ use std::{ffi::CStr, ptr::null}; +use allocator_api2::alloc::Allocator; use bstr::{BStr, BString, ByteSlice}; use fspy_shared_unix::exec::Exec; @@ -45,16 +46,16 @@ impl RawExec { fn to_c_str_array( mut strs: Vec, + allocator: impl Allocator, f: impl FnOnce(*const *const libc::c_char) -> R, ) -> R { // The pointer array exists only for the `f` call below, and building // it must not go through libc malloc: exec runs in the child of // `fork()` in multithreaded programs (`posix_spawn` forks then // execs), where malloc's lock may be held by a thread that no longer - // exists. A per-call arena has exactly this lifetime, and hands back - // the memory when the call ends. - let arena = fspy_nostd_alloc::pooled_bump(); - let mut ptr_vec = allocator_api2::vec::Vec::with_capacity_in(strs.len() + 1, &arena); + // exists. The interception's per-call bump has exactly this + // lifetime, and hands back the memory when the call ends. + let mut ptr_vec = allocator_api2::vec::Vec::with_capacity_in(strs.len() + 1, allocator); for s in &mut strs { s.push(0); ptr_vec.push(s.as_ptr().cast::()); @@ -92,7 +93,7 @@ impl RawExec { Exec { program, args, envs } } - pub fn from_exec(cmd: Exec, f: impl FnOnce(Self) -> R) -> R { + pub fn from_exec(cmd: Exec, allocator: impl Allocator, f: impl FnOnce(Self) -> R) -> R { let envs: Vec = cmd .envs .into_iter() @@ -107,8 +108,8 @@ impl RawExec { .collect(); Self::to_c_str(cmd.program, |prog| { - Self::to_c_str_array(cmd.args, |argv| { - Self::to_c_str_array(envs, |envp| f(Self { prog, argv, envp })) + Self::to_c_str_array(cmd.args, &allocator, |argv| { + Self::to_c_str_array(envs, &allocator, |envp| f(Self { prog, argv, envp })) }) }) } diff --git a/crates/fspy_preload_unix/Cargo.toml b/crates/fspy_preload_unix/Cargo.toml index b6bce4661..e74c0d632 100644 --- a/crates/fspy_preload_unix/Cargo.toml +++ b/crates/fspy_preload_unix/Cargo.toml @@ -15,6 +15,7 @@ fspy_shared_unix = { workspace = true } libc = { workspace = true } nix = { workspace = true, features = ["signal", "fs", "socket", "mman", "time"] } fspy_nostd = { workspace = true } +allocator-api2 = { workspace = true } fspy_nostd_alloc = { workspace = true } static_cell = { workspace = true } diff --git a/crates/fspy_preload_unix/src/client.rs b/crates/fspy_preload_unix/src/client.rs index e81a55a2d..734d3718d 100644 --- a/crates/fspy_preload_unix/src/client.rs +++ b/crates/fspy_preload_unix/src/client.rs @@ -32,9 +32,10 @@ pub unsafe fn handle_open(path: impl ToAbsolutePath, mode: impl ToAccessMode) { let _reset = ResetHandling(handling); if let Some(client) = global_client() { + let allocator = fspy_nostd_alloc::pooled_bump(); // SAFETY: path and mode contain valid pointers/values forwarded // from the interposed function's caller. - unsafe { client.try_handle_open(path, mode) }.unwrap(); + unsafe { client.try_handle_open(path, mode, allocator) }.unwrap(); } }); } diff --git a/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs b/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs index bd1506a37..729162d02 100644 --- a/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs +++ b/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs @@ -1,5 +1,6 @@ mod with_argv; +use allocator_api2::alloc::Allocator; use fspy_shared_unix::exec::ExecResolveConfig; use libc::{c_char, c_int}; use with_argv::with_argv; @@ -25,6 +26,7 @@ pub unsafe fn environ() -> *const *const c_char { } fn handle_exec( + allocator: impl Allocator, config: ExecResolveConfig, prog: *const libc::c_char, argv: *const *const libc::c_char, @@ -34,12 +36,17 @@ fn handle_exec( global_client().expect("exec unexpectedly called before client initialized in ctor"); // SAFETY: prog, argv, and envp are valid pointers to C strings/arrays forwarded from the interposed exec function let result = unsafe { - client.handle_exec(config, RawExec { prog, argv, envp }, |raw_command, pre_exec| { - if let Some(pre_exec) = pre_exec { - pre_exec.run()?; - } - Ok(execve::original()(raw_command.prog, raw_command.argv, raw_command.envp)) - }) + client.handle_exec( + config, + RawExec { prog, argv, envp }, + allocator, + |raw_command, pre_exec| { + if let Some(pre_exec) = pre_exec { + pre_exec.run()?; + } + Ok(execve::original()(raw_command.prog, raw_command.argv, raw_command.envp)) + }, + ) }; match result { Ok(ret) => ret, @@ -60,7 +67,13 @@ unsafe extern "C" fn execve( argv: *const *const libc::c_char, envp: *const *const libc::c_char, ) -> libc::c_int { - handle_exec(ExecResolveConfig::search_path_disabled(), prog, argv, envp) + handle_exec( + fspy_nostd_alloc::pooled_bump(), + ExecResolveConfig::search_path_disabled(), + prog, + argv, + envp, + ) } intercept!(execl(64): unsafe extern "C" fn(path: *const c_char, arg0: *const c_char, ...) -> c_int); @@ -73,7 +86,13 @@ unsafe extern "C" fn execl(path: *const c_char, arg0: *const c_char, valist: ... // SAFETY: valist and arg0 are valid variadic arguments forwarded from the interposed execl function unsafe { with_argv(valist, arg0, |args, _remaining| { - handle_exec(ExecResolveConfig::search_path_disabled(), path, args.as_ptr(), environ()) + handle_exec( + fspy_nostd_alloc::pooled_bump(), + ExecResolveConfig::search_path_disabled(), + path, + args.as_ptr(), + environ(), + ) }) } } @@ -89,6 +108,7 @@ unsafe extern "C" fn execlp(path: *const c_char, arg0: *const c_char, valist: .. unsafe { with_argv(valist, arg0, |args, _remaining| { handle_exec( + fspy_nostd_alloc::pooled_bump(), ExecResolveConfig::search_path_enabled(None), path, args.as_ptr(), @@ -109,7 +129,13 @@ unsafe extern "C" fn execle(path: *const c_char, arg0: *const c_char, valist: .. unsafe { with_argv(valist, arg0, |args, mut remaining| { let envp = remaining.next_arg::<*const *const c_char>(); - handle_exec(ExecResolveConfig::search_path_disabled(), path, args.as_ptr(), envp) + handle_exec( + fspy_nostd_alloc::pooled_bump(), + ExecResolveConfig::search_path_disabled(), + path, + args.as_ptr(), + envp, + ) }) } } @@ -122,7 +148,15 @@ unsafe extern "C" fn execv(path: *const c_char, argv: *const *const c_char) -> c )] let _unused = execv::original; // SAFETY: path, argv are valid pointers forwarded from the interposed function; environ() returns the process environment - unsafe { handle_exec(ExecResolveConfig::search_path_disabled(), path, argv, environ()) } + unsafe { + handle_exec( + fspy_nostd_alloc::pooled_bump(), + ExecResolveConfig::search_path_disabled(), + path, + argv, + environ(), + ) + } } intercept!(execvp(64): unsafe extern "C" fn( @@ -136,7 +170,13 @@ unsafe extern "C" fn execvp(prog: *const c_char, argv: *const *const c_char) -> )] let _unused = execvp::original; // SAFETY: environ() returns the valid process environment pointer - handle_exec(ExecResolveConfig::search_path_enabled(None), prog, argv, unsafe { environ() }) + handle_exec( + fspy_nostd_alloc::pooled_bump(), + ExecResolveConfig::search_path_enabled(None), + prog, + argv, + unsafe { environ() }, + ) } #[cfg(target_os = "linux")] @@ -171,7 +211,13 @@ mod linux_only { reason = "suppresses unused warning on *::original" )] let _unused = execvpe::original; - handle_exec(ExecResolveConfig::search_path_enabled(None), file, argv, envp) + handle_exec( + fspy_nostd_alloc::pooled_bump(), + ExecResolveConfig::search_path_enabled(None), + file, + argv, + envp, + ) } intercept!(execveat(64): unsafe extern "C" fn( dirfd: c_int, @@ -211,6 +257,7 @@ mod linux_only { // `abs_path` is a C string, so the exec receives a terminated // pointer by construction rather than by convention. handle_exec( + &arena, ExecResolveConfig::search_path_disabled(), abs_path.as_ptr().cast(), argv.cast(), @@ -235,6 +282,12 @@ mod linux_only { let _unused = fexecve::original; let prog = format!("/proc/self/fd/{fd}\0"); let prog = prog.as_ptr(); - handle_exec(ExecResolveConfig::search_path_disabled(), prog.cast(), argv, envp) + handle_exec( + fspy_nostd_alloc::pooled_bump(), + ExecResolveConfig::search_path_disabled(), + prog.cast(), + argv, + envp, + ) } } diff --git a/crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs b/crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs index 9496b1153..e76373ff7 100644 --- a/crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs +++ b/crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs @@ -47,6 +47,7 @@ unsafe fn handle_posix_spawn( client.handle_exec::( config, RawExec { prog: file, argv: argv.cast(), envp: envp.cast() }, + fspy_nostd_alloc::pooled_bump(), |raw_command, pre_exec| { let call_original = move || { original(