From 6e283d4878bd2105df1f92d63a4280ebc8cbfbcb Mon Sep 17 00:00:00 2001 From: wan9chi Date: Mon, 26 Oct 2026 16:14:01 +0900 Subject: [PATCH] fix(fspy): format the fexecve path without allocating The fexecve interposer built `/proc/self/fd/{fd}` with `format!`, which goes through libc malloc. Exec may run in the child of fork(), where malloc's lock may be held by a thread that no longer exists. Format it into a stack buffer with the existing `proc_fd_path` helper instead, which now takes the raw descriptor so `-1` formats as before. Co-Authored-By: Claude Opus 5.5 --- crates/fspy_client_unix/src/convert.rs | 15 ++++++++------- .../src/interceptions/spawn/exec/mod.rs | 8 ++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/fspy_client_unix/src/convert.rs b/crates/fspy_client_unix/src/convert.rs index d8f28b6a7..6b3e72c93 100644 --- a/crates/fspy_client_unix/src/convert.rs +++ b/crates/fspy_client_unix/src/convert.rs @@ -15,7 +15,7 @@ fn get_fd_path(allocator: A, fd: BorrowedFd<'_>) -> nix::Result Ok(Some(path)), Err(fspy_nostd::Error::BADF | fspy_nostd::Error::NOENT) => Ok(None), @@ -24,18 +24,19 @@ fn get_fd_path(allocator: A, fd: BorrowedFd<'_>) -> nix::Result::MAX_STR_LEN + 1; +/// Formats `/proc/self/fd/{fd}` into `buf`, without allocating. #[cfg(target_os = "linux")] -fn proc_fd_path<'buf>( - fd: BorrowedFd<'_>, - buf: &'buf mut [u8; PROC_FD_PATH_CAPACITY], -) -> fspy_nostd::CStr<'buf, fspy_nostd::Thin> { +pub fn proc_fd_path( + fd: c_int, + buf: &mut [u8; PROC_FD_PATH_CAPACITY], +) -> fspy_nostd::CStr<'_, fspy_nostd::Thin> { const PREFIX: &[u8] = b"/proc/self/fd/"; let mut formatted = itoa::Buffer::new(); - let fd = formatted.format(fd.as_raw_fd()).as_bytes(); + let fd = formatted.format(fd).as_bytes(); buf[..PREFIX.len()].copy_from_slice(PREFIX); let end = PREFIX.len() + fd.len(); 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 51ed0f942..c94f81aa9 100644 --- a/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs +++ b/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs @@ -200,7 +200,7 @@ mod linux_only { reason = "macro-generated code requires types from parent scope" )] use super::*; - use crate::client::convert::{PathAt, ToAbsolutePath}; + use crate::client::convert::{PROC_FD_PATH_CAPACITY, PathAt, ToAbsolutePath, proc_fd_path}; intercept!(execvpe(64): unsafe extern "C" fn( prog: *const libc::c_char, @@ -286,12 +286,12 @@ mod linux_only { reason = "suppresses unused warning on *::original" )] let _unused = fexecve::original; - let prog = format!("/proc/self/fd/{fd}\0"); - let prog = prog.as_ptr(); + let mut prog = [0; PROC_FD_PATH_CAPACITY]; + let prog = proc_fd_path(fd, &mut prog); handle_exec( fspy_nostd_alloc::pooled_bump(), ExecResolveConfig::search_path_disabled(), - prog.cast(), + prog.as_ptr().cast(), argv, envp, )