Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions crates/fspy_client_unix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ impl<'a> Client<'a> {
&self,
config: ExecResolveConfig,
raw_exec: RawExec,
allocator: impl Allocator,
f: impl FnOnce(RawExec, Option<PreExec>) -> nix::Result<R>,
) -> nix::Result<R> {
// SAFETY: raw_exec contains valid pointers to C strings and
Expand All @@ -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.
Expand All @@ -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())));
Expand Down
15 changes: 8 additions & 7 deletions crates/fspy_client_unix/src/raw_exec.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -45,16 +46,16 @@ impl RawExec {

fn to_c_str_array<R>(
mut strs: Vec<BString>,
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::<libc::c_char>());
Expand Down Expand Up @@ -92,7 +93,7 @@ impl RawExec {
Exec { program, args, envs }
}

pub fn from_exec<R>(cmd: Exec, f: impl FnOnce(Self) -> R) -> R {
pub fn from_exec<R>(cmd: Exec, allocator: impl Allocator, f: impl FnOnce(Self) -> R) -> R {
let envs: Vec<BString> = cmd
.envs
.into_iter()
Expand All @@ -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 }))
})
})
}
Expand Down
1 change: 1 addition & 0 deletions crates/fspy_preload_unix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
3 changes: 2 additions & 1 deletion crates/fspy_preload_unix/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
});
}
Expand Down
79 changes: 66 additions & 13 deletions crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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(),
)
})
}
}
Expand All @@ -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(),
Expand All @@ -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,
)
})
}
}
Expand All @@ -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(
Expand All @@ -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")]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ unsafe fn handle_posix_spawn(
client.handle_exec::<c_int>(
config,
RawExec { prog: file, argv: argv.cast(), envp: envp.cast() },
fspy_nostd_alloc::pooled_bump(),
|raw_command, pre_exec| {
let call_original = move || {
original(
Expand Down
Loading