Skip to content

Commit cd1446d

Browse files
wan9chiclaude
andcommitted
refactor(fspy-shared): make the payload a borrowed view
The payload is now a view over one buffer somebody owns, the model the Windows preload already had with its static Detours page: - ChannelConf borrows its two paths; channel() returns only the Receiver, and Receiver::conf() derives the configuration from receiver-owned C strings (the lock path now stored beside the keeper path, and generated absolute like the shm path already was). - The unix Payload borrows every path it carries: preload_path and the macOS artifacts join the conf as views. DecodedPayload owns the decoded bytes plus the env string, and both the client and the supervisor derive transient Payload views instead of retaining owned copies — the supervisor lends its session paths, the client re-reads views from its buffer per exec. seccomp_payload stays owned until fspy_seccomp_unotify grows borrowed types. - The Windows preload deserializes its payload zero-copy from the static page and forwards those original bytes to children instead of re-serializing per spawn. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 0fd7673 commit cd1446d

10 files changed

Lines changed: 181 additions & 111 deletions

File tree

crates/fspy/src/unix/mod.rs

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ use std::{io, path::Path};
88

99
#[cfg(target_os = "linux")]
1010
use fspy_seccomp_unotify::supervisor::supervise;
11+
#[cfg(target_os = "macos")]
1112
use fspy_shared::ipc::PathAccess;
1213
#[cfg(not(target_env = "musl"))]
1314
use fspy_shared::ipc::{IpcStr, channel::channel};
14-
#[cfg(target_os = "macos")]
15-
use fspy_shared_unix::payload::Artifacts;
1615
use fspy_shared_unix::{
1716
exec::ExecResolveConfig,
1817
payload::{Payload, encode_payload},
@@ -29,9 +28,12 @@ use crate::ipc::{OwnedReceiverLockGuard, SHM_CAPACITY};
2928
use crate::{ChildTermination, Command, TrackedChild, arena::PathAccessArena, error::SpawnError};
3029

3130
#[derive(Debug)]
31+
#[expect(clippy::struct_field_names, reason = "each field names a distinct injected path")]
3232
pub struct SpyImpl {
3333
#[cfg(target_os = "macos")]
34-
artifacts: Artifacts,
34+
bash_path: Box<IpcStr>,
35+
#[cfg(target_os = "macos")]
36+
coreutils_path: Box<IpcStr>,
3537

3638
#[cfg(not(target_env = "musl"))]
3739
preload_path: Box<IpcStr>,
@@ -58,15 +60,19 @@ impl SpyImpl {
5860
#[cfg(not(target_env = "musl"))]
5961
preload_path,
6062
#[cfg(target_os = "macos")]
61-
artifacts: {
62-
let coreutils_path =
63-
macos_artifacts::COREUTILS_BINARY.materialize().executable().at(dir)?;
64-
let bash_path = macos_artifacts::OILS_BINARY.materialize().executable().at(dir)?;
65-
Artifacts {
66-
bash_path: bash_path.as_path().into(),
67-
coreutils_path: coreutils_path.as_path().into(),
68-
}
69-
},
63+
bash_path: macos_artifacts::OILS_BINARY
64+
.materialize()
65+
.executable()
66+
.at(dir)?
67+
.as_path()
68+
.into(),
69+
#[cfg(target_os = "macos")]
70+
coreutils_path: macos_artifacts::COREUTILS_BINARY
71+
.materialize()
72+
.executable()
73+
.at(dir)?
74+
.as_path()
75+
.into(),
7076
})
7177
}
7278

@@ -79,31 +85,36 @@ impl SpyImpl {
7985
let supervisor = supervise::<SyscallHandler>().map_err(SpawnError::Supervisor)?;
8086

8187
#[cfg(not(target_env = "musl"))]
82-
let (ipc_channel_conf, ipc_receiver) =
83-
channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?;
88+
let ipc_receiver = channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?;
8489

8590
let payload = Payload {
8691
#[cfg(not(target_env = "musl"))]
87-
ipc_channel_conf,
88-
89-
#[cfg(target_os = "macos")]
90-
artifacts: self.artifacts.clone(),
92+
ipc_channel_conf: ipc_receiver.conf(),
93+
#[cfg(target_env = "musl")]
94+
ipc_channel_conf: core::marker::PhantomData,
9195

9296
#[cfg(not(target_env = "musl"))]
93-
preload_path: self.preload_path.clone(),
97+
preload_path: &self.preload_path,
98+
99+
#[cfg(target_os = "macos")]
100+
artifacts: fspy_shared_unix::payload::Artifacts {
101+
bash_path: &self.bash_path,
102+
coreutils_path: &self.coreutils_path,
103+
},
94104

95105
#[cfg(target_os = "linux")]
96106
seccomp_payload: supervisor.payload().clone(),
97107
};
98108

99-
let encoded_payload = encode_payload(payload);
109+
let encoded_string = encode_payload(&payload);
100110

101111
let mut exec = command.get_exec();
102112
let mut exec_resolve_accesses = PathAccessArena::default();
103113
let pre_exec = handle_exec(
104114
&mut exec,
105115
ExecResolveConfig::search_path_enabled(None),
106-
&encoded_payload,
116+
&payload,
117+
encoded_string.as_ref(),
107118
|mode, path| {
108119
exec_resolve_accesses.add(PathAccess { mode, path: path.into() });
109120
},

crates/fspy/src/windows/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ impl SpyImpl {
8686

8787
command.creation_flags(CREATE_SUSPENDED);
8888

89-
let (channel_conf, receiver) =
90-
channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?;
89+
let receiver = channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?;
9190

9291
let mut spawn_success = false;
9392
let spawn_success = &mut spawn_success;
@@ -107,7 +106,7 @@ impl SpyImpl {
107106
}
108107

109108
let payload = Payload {
110-
channel_conf: channel_conf.clone(),
109+
channel_conf: receiver.conf(),
111110
ansi_dll_path_with_nul: ansi_dll_path_with_nul.to_bytes(),
112111
};
113112
let payload_bytes = wincode::serialize(&payload).unwrap();

crates/fspy_client_unix/src/lib.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ use convert::{ToAbsolutePath, ToAccessMode};
1414
use fspy_shared::ipc::{PathAccess, channel::Sender};
1515
use fspy_shared_unix::{
1616
exec::ExecResolveConfig,
17-
payload::{EncodedPayload, decode_payload_from_env},
17+
payload::{DecodedPayload, decode_payload_from_env},
1818
spawn::{PreExec, handle_exec},
1919
};
2020
use raw_exec::RawExec;
2121
use wincode::Serialize as _;
2222

2323
pub struct Client {
24-
encoded_payload: EncodedPayload,
24+
decoded_payload: DecodedPayload,
2525
ipc_sender: Option<Sender>,
2626
}
2727

@@ -51,9 +51,9 @@ impl Client {
5151
reason = "the client intentionally reports an unavailable supervisor channel"
5252
)]
5353
pub fn from_env(envs: impl Iterator<Item = fspy_nostd::env::Entry>) -> Self {
54-
let encoded_payload = decode_payload_from_env(envs).unwrap();
54+
let decoded_payload = decode_payload_from_env(envs).unwrap();
5555

56-
let ipc_sender = match encoded_payload.payload.ipc_channel_conf.sender() {
56+
let ipc_sender = match decoded_payload.payload().ipc_channel_conf.sender() {
5757
Ok(sender) => Some(sender),
5858
Err(err) => {
5959
// This can happen if the process starts after the root target
@@ -63,7 +63,7 @@ impl Client {
6363
}
6464
};
6565

66-
Self { encoded_payload, ipc_sender }
66+
Self { decoded_payload, ipc_sender }
6767
}
6868

6969
fn send(&self, mode: fspy_shared::ipc::AccessMode, path: &Path) -> anyhow::Result<()> {
@@ -119,9 +119,15 @@ impl Client {
119119
// SAFETY: raw_exec contains valid pointers to C strings and
120120
// null-terminated arrays, as provided by the caller.
121121
let mut exec = unsafe { raw_exec.to_exec() };
122-
let pre_exec = handle_exec(&mut exec, config, &self.encoded_payload, |mode, path| {
123-
self.send(mode, path).unwrap();
124-
})?;
122+
let pre_exec = handle_exec(
123+
&mut exec,
124+
config,
125+
&self.decoded_payload.payload(),
126+
self.decoded_payload.encoded_string.as_ref(),
127+
|mode, path| {
128+
self.send(mode, path).unwrap();
129+
},
130+
)?;
125131
RawExec::from_exec(exec, |raw_command| f(raw_command, pre_exec))
126132
}
127133

crates/fspy_preload_windows/src/windows/client.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use winapi::{shared::minwindef::BOOL, um::winnt::HANDLE};
99

1010
pub struct Client<'a> {
1111
payload: Payload<'a>,
12+
payload_bytes: &'a [u8],
1213
ipc_sender: Option<Sender>,
1314
}
1415

@@ -33,7 +34,7 @@ impl<'a> Client<'a> {
3334
}
3435
};
3536

36-
Self { payload, ipc_sender }
37+
Self { payload, payload_bytes, ipc_sender }
3738
}
3839

3940
pub fn send(&self, access: PathAccess<'_>) {
@@ -44,14 +45,15 @@ impl<'a> Client<'a> {
4445
}
4546

4647
pub unsafe fn prepare_child_process(&self, child_handle: HANDLE) -> BOOL {
47-
let payload_bytes = wincode::serialize(&self.payload).unwrap();
48+
// The payload propagates to children unchanged, so forward the bytes
49+
// this process was given instead of re-serializing.
4850
// SAFETY: FFI call to DetourCopyPayloadToProcess with valid handle and payload buffer
4951
unsafe {
5052
DetourCopyPayloadToProcess(
5153
child_handle,
5254
&PAYLOAD_ID,
53-
payload_bytes.as_ptr().cast(),
54-
payload_bytes.len().try_into().unwrap(),
55+
self.payload_bytes.as_ptr().cast(),
56+
self.payload_bytes.len().try_into().unwrap(),
5557
)
5658
}
5759
}

0 commit comments

Comments
 (0)