Skip to content

Commit eccff03

Browse files
wan9chiclaude
andcommitted
refactor(fspy-client): accept allocators from the interception layer
The pooled bumps that fspy_client_unix created internally for per-interception temporaries now come from the interception layer, so allocator choices live only at the top of the call stack: - try_handle_open, handle_exec, and RawExec::from_exec accept the allocator for their transient path and pointer-array storage. - The unix exec wrapper takes the allocator from each interception, so execveat shares its path-resolution bump with the exec-argument rebuild instead of drawing a second chunk from the pool. fspy_client_unix no longer creates any allocator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 9f03d6f commit eccff03

7 files changed

Lines changed: 83 additions & 24 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fspy_client_unix/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ impl<'a> Client<'a> {
120120
&self,
121121
config: ExecResolveConfig,
122122
raw_exec: RawExec,
123+
allocator: impl Allocator,
123124
f: impl FnOnce(RawExec, Option<PreExec>) -> nix::Result<R>,
124125
) -> nix::Result<R> {
125126
// SAFETY: raw_exec contains valid pointers to C strings and
@@ -128,7 +129,7 @@ impl<'a> Client<'a> {
128129
let pre_exec = handle_exec(&mut exec, config, &self.encoded_payload, |mode, path| {
129130
self.send(mode, path);
130131
})?;
131-
RawExec::from_exec(exec, |raw_command| f(raw_command, pre_exec))
132+
RawExec::from_exec(exec, allocator, |raw_command| f(raw_command, pre_exec))
132133
}
133134

134135
/// Resolves and reports one intercepted file access.
@@ -145,12 +146,12 @@ impl<'a> Client<'a> {
145146
&self,
146147
path: impl ToAbsolutePath,
147148
mode: impl ToAccessMode,
149+
allocator: impl Allocator,
148150
) -> anyhow::Result<()> {
149151
// SAFETY: mode contains a valid pointer (if ModeStr) or a plain value,
150152
// as provided by the caller.
151153
let mode = unsafe { mode.to_access_mode() };
152-
let arena = fspy_nostd_alloc::pooled_bump();
153-
let Some(abs_path) = path.to_absolute_path(&arena)? else {
154+
let Some(abs_path) = path.to_absolute_path(&allocator)? else {
154155
return Ok(());
155156
};
156157
self.send(mode, Path::new(OsStr::from_bytes(abs_path.as_units())));

crates/fspy_client_unix/src/raw_exec.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{ffi::CStr, ptr::null};
22

3+
use allocator_api2::alloc::Allocator;
34
use bstr::{BStr, BString, ByteSlice};
45
use fspy_shared_unix::exec::Exec;
56

@@ -45,16 +46,16 @@ impl RawExec {
4546

4647
fn to_c_str_array<R>(
4748
mut strs: Vec<BString>,
49+
allocator: impl Allocator,
4850
f: impl FnOnce(*const *const libc::c_char) -> R,
4951
) -> R {
5052
// The pointer array exists only for the `f` call below, and building
5153
// it must not go through libc malloc: exec runs in the child of
5254
// `fork()` in multithreaded programs (`posix_spawn` forks then
5355
// execs), where malloc's lock may be held by a thread that no longer
54-
// exists. A per-call arena has exactly this lifetime, and hands back
55-
// the memory when the call ends.
56-
let arena = fspy_nostd_alloc::pooled_bump();
57-
let mut ptr_vec = allocator_api2::vec::Vec::with_capacity_in(strs.len() + 1, &arena);
56+
// exists. The interception's per-call bump has exactly this
57+
// lifetime, and hands back the memory when the call ends.
58+
let mut ptr_vec = allocator_api2::vec::Vec::with_capacity_in(strs.len() + 1, allocator);
5859
for s in &mut strs {
5960
s.push(0);
6061
ptr_vec.push(s.as_ptr().cast::<libc::c_char>());
@@ -92,7 +93,7 @@ impl RawExec {
9293
Exec { program, args, envs }
9394
}
9495

95-
pub fn from_exec<R>(cmd: Exec, f: impl FnOnce(Self) -> R) -> R {
96+
pub fn from_exec<R>(cmd: Exec, allocator: impl Allocator, f: impl FnOnce(Self) -> R) -> R {
9697
let envs: Vec<BString> = cmd
9798
.envs
9899
.into_iter()
@@ -107,8 +108,8 @@ impl RawExec {
107108
.collect();
108109

109110
Self::to_c_str(cmd.program, |prog| {
110-
Self::to_c_str_array(cmd.args, |argv| {
111-
Self::to_c_str_array(envs, |envp| f(Self { prog, argv, envp }))
111+
Self::to_c_str_array(cmd.args, &allocator, |argv| {
112+
Self::to_c_str_array(envs, &allocator, |envp| f(Self { prog, argv, envp }))
112113
})
113114
})
114115
}

crates/fspy_preload_unix/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ fspy_shared_unix = { workspace = true }
1515
libc = { workspace = true }
1616
nix = { workspace = true, features = ["signal", "fs", "socket", "mman", "time"] }
1717
fspy_nostd = { workspace = true }
18+
allocator-api2 = { workspace = true }
1819
fspy_nostd_alloc = { workspace = true }
1920

2021
[lints]

crates/fspy_preload_unix/src/client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ pub unsafe fn handle_open(path: impl ToAbsolutePath, mode: impl ToAccessMode) {
3434
let _reset = ResetHandling(handling);
3535

3636
if let Some(client) = global_client() {
37+
let allocator = fspy_nostd_alloc::pooled_bump();
3738
// SAFETY: path and mode contain valid pointers/values forwarded
3839
// from the interposed function's caller.
39-
unsafe { client.try_handle_open(path, mode) }.unwrap();
40+
unsafe { client.try_handle_open(path, mode, allocator) }.unwrap();
4041
}
4142
});
4243
}

crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod with_argv;
22

3+
use allocator_api2::alloc::Allocator;
34
use fspy_shared_unix::exec::ExecResolveConfig;
45
use libc::{c_char, c_int};
56
use with_argv::with_argv;
@@ -25,6 +26,7 @@ pub unsafe fn environ() -> *const *const c_char {
2526
}
2627

2728
fn handle_exec(
29+
allocator: impl Allocator,
2830
config: ExecResolveConfig,
2931
prog: *const libc::c_char,
3032
argv: *const *const libc::c_char,
@@ -34,12 +36,17 @@ fn handle_exec(
3436
global_client().expect("exec unexpectedly called before client initialized in ctor");
3537
// SAFETY: prog, argv, and envp are valid pointers to C strings/arrays forwarded from the interposed exec function
3638
let result = unsafe {
37-
client.handle_exec(config, RawExec { prog, argv, envp }, |raw_command, pre_exec| {
38-
if let Some(pre_exec) = pre_exec {
39-
pre_exec.run()?;
40-
}
41-
Ok(execve::original()(raw_command.prog, raw_command.argv, raw_command.envp))
42-
})
39+
client.handle_exec(
40+
config,
41+
RawExec { prog, argv, envp },
42+
allocator,
43+
|raw_command, pre_exec| {
44+
if let Some(pre_exec) = pre_exec {
45+
pre_exec.run()?;
46+
}
47+
Ok(execve::original()(raw_command.prog, raw_command.argv, raw_command.envp))
48+
},
49+
)
4350
};
4451
match result {
4552
Ok(ret) => ret,
@@ -60,7 +67,13 @@ unsafe extern "C" fn execve(
6067
argv: *const *const libc::c_char,
6168
envp: *const *const libc::c_char,
6269
) -> libc::c_int {
63-
handle_exec(ExecResolveConfig::search_path_disabled(), prog, argv, envp)
70+
handle_exec(
71+
fspy_nostd_alloc::pooled_bump(),
72+
ExecResolveConfig::search_path_disabled(),
73+
prog,
74+
argv,
75+
envp,
76+
)
6477
}
6578

6679
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: ...
7386
// SAFETY: valist and arg0 are valid variadic arguments forwarded from the interposed execl function
7487
unsafe {
7588
with_argv(valist, arg0, |args, _remaining| {
76-
handle_exec(ExecResolveConfig::search_path_disabled(), path, args.as_ptr(), environ())
89+
handle_exec(
90+
fspy_nostd_alloc::pooled_bump(),
91+
ExecResolveConfig::search_path_disabled(),
92+
path,
93+
args.as_ptr(),
94+
environ(),
95+
)
7796
})
7897
}
7998
}
@@ -89,6 +108,7 @@ unsafe extern "C" fn execlp(path: *const c_char, arg0: *const c_char, valist: ..
89108
unsafe {
90109
with_argv(valist, arg0, |args, _remaining| {
91110
handle_exec(
111+
fspy_nostd_alloc::pooled_bump(),
92112
ExecResolveConfig::search_path_enabled(None),
93113
path,
94114
args.as_ptr(),
@@ -109,7 +129,13 @@ unsafe extern "C" fn execle(path: *const c_char, arg0: *const c_char, valist: ..
109129
unsafe {
110130
with_argv(valist, arg0, |args, mut remaining| {
111131
let envp = remaining.next_arg::<*const *const c_char>();
112-
handle_exec(ExecResolveConfig::search_path_disabled(), path, args.as_ptr(), envp)
132+
handle_exec(
133+
fspy_nostd_alloc::pooled_bump(),
134+
ExecResolveConfig::search_path_disabled(),
135+
path,
136+
args.as_ptr(),
137+
envp,
138+
)
113139
})
114140
}
115141
}
@@ -122,7 +148,15 @@ unsafe extern "C" fn execv(path: *const c_char, argv: *const *const c_char) -> c
122148
)]
123149
let _unused = execv::original;
124150
// SAFETY: path, argv are valid pointers forwarded from the interposed function; environ() returns the process environment
125-
unsafe { handle_exec(ExecResolveConfig::search_path_disabled(), path, argv, environ()) }
151+
unsafe {
152+
handle_exec(
153+
fspy_nostd_alloc::pooled_bump(),
154+
ExecResolveConfig::search_path_disabled(),
155+
path,
156+
argv,
157+
environ(),
158+
)
159+
}
126160
}
127161

128162
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) ->
136170
)]
137171
let _unused = execvp::original;
138172
// SAFETY: environ() returns the valid process environment pointer
139-
handle_exec(ExecResolveConfig::search_path_enabled(None), prog, argv, unsafe { environ() })
173+
handle_exec(
174+
fspy_nostd_alloc::pooled_bump(),
175+
ExecResolveConfig::search_path_enabled(None),
176+
prog,
177+
argv,
178+
unsafe { environ() },
179+
)
140180
}
141181

142182
#[cfg(target_os = "linux")]
@@ -171,7 +211,13 @@ mod linux_only {
171211
reason = "suppresses unused warning on *::original"
172212
)]
173213
let _unused = execvpe::original;
174-
handle_exec(ExecResolveConfig::search_path_enabled(None), file, argv, envp)
214+
handle_exec(
215+
fspy_nostd_alloc::pooled_bump(),
216+
ExecResolveConfig::search_path_enabled(None),
217+
file,
218+
argv,
219+
envp,
220+
)
175221
}
176222
intercept!(execveat(64): unsafe extern "C" fn(
177223
dirfd: c_int,
@@ -211,6 +257,7 @@ mod linux_only {
211257
// `abs_path` is a C string, so the exec receives a terminated
212258
// pointer by construction rather than by convention.
213259
handle_exec(
260+
&arena,
214261
ExecResolveConfig::search_path_disabled(),
215262
abs_path.as_ptr().cast(),
216263
argv.cast(),
@@ -235,6 +282,12 @@ mod linux_only {
235282
let _unused = fexecve::original;
236283
let prog = format!("/proc/self/fd/{fd}\0");
237284
let prog = prog.as_ptr();
238-
handle_exec(ExecResolveConfig::search_path_disabled(), prog.cast(), argv, envp)
285+
handle_exec(
286+
fspy_nostd_alloc::pooled_bump(),
287+
ExecResolveConfig::search_path_disabled(),
288+
prog.cast(),
289+
argv,
290+
envp,
291+
)
239292
}
240293
}

crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ unsafe fn handle_posix_spawn(
4747
client.handle_exec::<c_int>(
4848
config,
4949
RawExec { prog: file, argv: argv.cast(), envp: envp.cast() },
50+
fspy_nostd_alloc::pooled_bump(),
5051
|raw_command, pre_exec| {
5152
let call_original = move || {
5253
original(

0 commit comments

Comments
 (0)