-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathmod.rs
More file actions
185 lines (158 loc) · 6.44 KB
/
Copy pathmod.rs
File metadata and controls
185 lines (158 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#[cfg(target_os = "linux")]
mod syscall_handler;
#[cfg(target_os = "macos")]
mod macos_artifacts;
use std::{io, path::Path};
#[cfg(target_os = "linux")]
use fspy_seccomp_unotify::supervisor::supervise;
#[cfg(not(target_env = "musl"))]
use fspy_shared::ipc::NativeStr;
use fspy_shared::ipc::PathAccess;
#[cfg(target_os = "macos")]
use fspy_shared_unix::payload::Artifacts;
use fspy_shared_unix::{
exec::ExecResolveConfig,
payload::{Payload, encode_payload},
spawn::handle_exec,
};
use futures_util::FutureExt;
#[cfg(target_os = "linux")]
use syscall_handler::SyscallHandler;
use tokio::task::spawn_blocking;
use tokio_util::sync::CancellationToken;
#[cfg(not(target_env = "musl"))]
use crate::ipc::IpcSupervisor;
use crate::{ChildTermination, Command, TrackedChild, arena::PathAccessArena, error::SpawnError};
#[derive(Debug)]
pub struct SpyImpl {
#[cfg(target_os = "macos")]
artifacts: Artifacts,
#[cfg(not(target_env = "musl"))]
preload_path: Box<NativeStr>,
}
impl SpyImpl {
/// Initialize the fs access spy by writing the preload library on disk.
///
/// On musl targets, we don't build a preload library —
/// only seccomp-based tracking is used.
pub fn init_in(#[cfg_attr(target_env = "musl", allow(unused))] dir: &Path) -> io::Result<Self> {
#[cfg(not(target_env = "musl"))]
let preload_path = {
use materialized_artifact::{Artifact, artifact};
const PRELOAD_CDYLIB: Artifact = artifact!("fspy_preload");
let preload_cdylib_path = PRELOAD_CDYLIB.materialize().suffix(".dylib").at(dir)?;
preload_cdylib_path.as_path().into()
};
Ok(Self {
#[cfg(not(target_env = "musl"))]
preload_path,
#[cfg(target_os = "macos")]
artifacts: {
let coreutils_path =
macos_artifacts::COREUTILS_BINARY.materialize().executable().at(dir)?;
let bash_path = macos_artifacts::OILS_BINARY.materialize().executable().at(dir)?;
Artifacts {
bash_path: bash_path.as_path().into(),
coreutils_path: coreutils_path.as_path().into(),
}
},
})
}
pub(crate) async fn spawn(
&self,
mut command: Command,
cancellation_token: CancellationToken,
) -> Result<TrackedChild, SpawnError> {
#[cfg(target_os = "linux")]
let supervisor = supervise::<SyscallHandler>().map_err(SpawnError::Supervisor)?;
#[cfg(not(target_env = "musl"))]
let ipc_supervisor = IpcSupervisor::bind().map_err(SpawnError::IpcServer)?;
let payload = Payload {
#[cfg(not(target_env = "musl"))]
server_name: ipc_supervisor.server_name().to_cow_os_str().into_owned().into(),
#[cfg(target_os = "macos")]
artifacts: self.artifacts.clone(),
#[cfg(not(target_env = "musl"))]
preload_path: self.preload_path.clone(),
#[cfg(target_os = "linux")]
seccomp_payload: supervisor.payload().clone(),
};
let encoded_payload = encode_payload(payload);
let mut exec = command.get_exec();
let mut exec_resolve_accesses = PathAccessArena::default();
let pre_exec = handle_exec(
&mut exec,
ExecResolveConfig::search_path_enabled(None),
&encoded_payload,
|mode, path| {
exec_resolve_accesses.add(PathAccess { mode, path: path.into() });
},
)
.map_err(|err| SpawnError::Injection(err.into()))?;
command.set_exec(exec);
command.env("FSPY", "1");
let mut tokio_command = command.into_tokio_command();
// SAFETY: the pre_exec closure only calls pre_exec.run() which is safe to call in a fork context
unsafe {
tokio_command.pre_exec(move || {
if let Some(pre_exec) = pre_exec.as_ref() {
pre_exec.run()?;
}
Ok(())
});
}
// tokio_command.spawn blocks while executing the `pre_exec` closure.
// Run it inside spawn_blocking to avoid blocking the tokio runtime, especially the supervisor loop,
// which needs to accept incoming connections while `pre_exec` is connecting to it.
let mut child = spawn_blocking(move || tokio_command.spawn())
.await
.map_err(|err| SpawnError::OsSpawn(err.into()))?
.map_err(SpawnError::OsSpawn)?;
Ok(TrackedChild {
stdin: child.stdin.take(),
stdout: child.stdout.take(),
stderr: child.stderr.take(),
// Keep polling for the child to exit in the background even if `wait_handle` is not awaited,
// because we need to stop accepting IPC connections as soon as the child exits.
wait_handle: tokio::spawn(async move {
let status = tokio::select! {
status = child.wait() => status?,
() = cancellation_token.cancelled() => {
child.start_kill()?;
child.wait().await?
}
};
#[cfg(not(target_env = "musl"))]
let mut ipc_arenas = ipc_supervisor.stop().await?;
let arenas = std::iter::once(exec_resolve_accesses);
// Stop the supervisor and collect path accesses from it.
#[cfg(target_os = "linux")]
let arenas = arenas.chain(
supervisor
.stop()
.await?
.into_iter()
.map(syscall_handler::SyscallHandler::into_arena),
);
let arenas = arenas.collect::<Vec<_>>();
#[cfg(not(target_env = "musl"))]
let arenas = {
ipc_arenas.extend(arenas);
ipc_arenas
};
let path_accesses = PathAccessIterable { arenas };
io::Result::Ok(ChildTermination { status, path_accesses })
})
.map(|f| f?) // flatten JoinError and io::Result
.boxed(),
})
}
}
pub struct PathAccessIterable {
arenas: Vec<PathAccessArena>,
}
impl PathAccessIterable {
pub fn iter(&self) -> impl Iterator<Item = PathAccess<'_>> {
self.arenas.iter().flat_map(|arena| arena.borrow_accesses().iter()).copied()
}
}