1use std::ffi::OsStr;
6use std::{env, process};
7
8#[cfg(any(
9 target_os = "macos",
10 all(
11 not(target_os = "windows"),
12 not(target_os = "ios"),
13 not(target_os = "android"),
14 not(target_env = "ohos"),
15 not(target_arch = "arm"),
16 not(target_arch = "aarch64"),
17 not(target_arch = "riscv32"),
18 not(target_arch = "riscv64")
19 )
20))]
21use gaol::profile::{Operation, PathPattern, Profile};
22use ipc_channel::IpcError;
23use serde::{Deserialize, Serialize};
24use servo_config::opts::Opts;
25use servo_config::prefs::Preferences;
26
27use crate::event_loop::NewScriptEventLoopProcessInfo;
28use crate::process_manager::Process;
29use crate::serviceworker::ServiceWorkerUnprivilegedContent;
30
31#[derive(Deserialize, Serialize)]
32#[expect(clippy::large_enum_variant)]
33pub enum UnprivilegedContent {
34 ScriptEventLoop(NewScriptEventLoopProcessInfo),
35 ServiceWorker(ServiceWorkerUnprivilegedContent),
36}
37
38impl UnprivilegedContent {
39 pub fn opts(&self) -> Opts {
40 match self {
41 UnprivilegedContent::ScriptEventLoop(content) => content.opts.clone(),
42 UnprivilegedContent::ServiceWorker(content) => content.opts.clone(),
43 }
44 }
45
46 pub fn prefs(&self) -> &Preferences {
47 match self {
48 UnprivilegedContent::ScriptEventLoop(content) => &content.prefs,
49 UnprivilegedContent::ServiceWorker(content) => &content.prefs,
50 }
51 }
52}
53
54#[cfg(target_os = "macos")]
56pub fn content_process_sandbox_profile() -> Profile {
57 use std::path::PathBuf;
58
59 use embedder_traits::resources;
60 use gaol::platform;
61
62 let mut operations = vec![
63 Operation::FileReadAll(PathPattern::Literal(PathBuf::from("/dev/urandom"))),
64 Operation::FileReadAll(PathPattern::Subpath(PathBuf::from("/Library/Fonts"))),
65 Operation::FileReadAll(PathPattern::Subpath(PathBuf::from("/System/Library/Fonts"))),
66 Operation::FileReadAll(PathPattern::Subpath(PathBuf::from(
67 "/System/Library/Frameworks/ApplicationServices.framework",
68 ))),
69 Operation::FileReadAll(PathPattern::Subpath(PathBuf::from(
70 "/System/Library/Frameworks/CoreGraphics.framework",
71 ))),
72 Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/"))),
73 Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/Library"))),
74 Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/System"))),
75 Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/etc"))),
76 Operation::SystemInfoRead,
77 Operation::PlatformSpecific(platform::macos::Operation::MachLookup(
78 b"com.apple.FontServer".to_vec(),
79 )),
80 ];
81
82 operations.extend(
83 resources::sandbox_access_files()
84 .into_iter()
85 .map(|p| Operation::FileReadAll(PathPattern::Literal(p))),
86 );
87 operations.extend(
88 resources::sandbox_access_files_dirs()
89 .into_iter()
90 .map(|p| Operation::FileReadAll(PathPattern::Subpath(p))),
91 );
92
93 Profile::new(operations).expect("Failed to create sandbox profile!")
94}
95
96#[cfg(all(
98 not(target_os = "macos"),
99 not(target_os = "windows"),
100 not(target_os = "ios"),
101 not(target_os = "android"),
102 not(target_env = "ohos"),
103 not(target_arch = "arm"),
104 not(target_arch = "aarch64"),
105 not(target_arch = "riscv32"),
106 not(target_arch = "riscv64")
107))]
108pub fn content_process_sandbox_profile() -> Profile {
109 use std::path::PathBuf;
110
111 use embedder_traits::resources;
112
113 let mut operations = vec![Operation::FileReadAll(PathPattern::Literal(PathBuf::from(
114 "/dev/urandom",
115 )))];
116
117 operations.extend(
118 resources::sandbox_access_files()
119 .into_iter()
120 .map(|p| Operation::FileReadAll(PathPattern::Literal(p))),
121 );
122 operations.extend(
123 resources::sandbox_access_files_dirs()
124 .into_iter()
125 .map(|p| Operation::FileReadAll(PathPattern::Subpath(p))),
126 );
127
128 Profile::new(operations).expect("Failed to create sandbox profile!")
129}
130
131#[cfg(any(
132 target_os = "windows",
133 target_os = "ios",
134 target_os = "android",
135 target_env = "ohos",
136 target_arch = "arm",
137 target_arch = "riscv32",
138 target_arch = "riscv64",
139
140 all(target_arch = "aarch64", not(target_os = "macos"))
142))]
143pub fn content_process_sandbox_profile() {
144 log::error!("Sandboxed multiprocess is not supported on this platform.");
145 process::exit(1);
146}
147
148#[cfg(any(
149 target_os = "windows",
150 target_os = "android",
151 target_env = "ohos",
152 target_arch = "arm",
153 target_arch = "aarch64",
154 target_arch = "riscv32",
155 target_arch = "riscv64"
156))]
157pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<Process, IpcError> {
158 use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
159 let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
163 .expect("Failed to create IPC one-shot server.");
164
165 let path_to_self = env::current_exe().expect("Failed to get current executor.");
166 let mut child_process = process::Command::new(path_to_self);
167 setup_common(&mut child_process, token);
168
169 #[allow(clippy::zombie_processes)]
170 let child = child_process
171 .spawn()
172 .expect("Failed to start unsandboxed child process!");
173
174 let (_receiver, sender) = server.accept().expect("Server failed to accept.");
175 sender.send(content)?;
176
177 Ok(Process::Unsandboxed(child))
178}
179
180#[cfg(all(
181 not(target_os = "windows"),
182 not(target_os = "ios"),
183 not(target_os = "android"),
184 not(target_env = "ohos"),
185 not(target_arch = "arm"),
186 not(target_arch = "aarch64"),
187 not(target_arch = "riscv32"),
188 not(target_arch = "riscv64")
189))]
190pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<Process, IpcError> {
191 use gaol::sandbox::{self, Sandbox, SandboxMethods};
192 use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
193
194 #[allow(non_local_definitions)]
197 impl CommandMethods for gaol::sandbox::Command {
198 fn arg<T>(&mut self, arg: T)
199 where
200 T: AsRef<OsStr>,
201 {
202 self.arg(arg);
203 }
204
205 fn env<T, U>(&mut self, key: T, val: U)
206 where
207 T: AsRef<OsStr>,
208 U: AsRef<OsStr>,
209 {
210 self.env(key, val);
211 }
212 }
213
214 let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
218 .expect("Failed to create IPC one-shot server.");
219
220 let process = if content.opts().sandbox {
222 let mut command = sandbox::Command::me().expect("Failed to get current sandbox.");
223 setup_common(&mut command, token);
224
225 let profile = content_process_sandbox_profile();
226 Process::Sandboxed(
227 Sandbox::new(profile)
228 .start(&mut command)
229 .expect("Failed to start sandboxed child process!")
230 .pid as u32,
231 )
232 } else {
233 let path_to_self = env::current_exe().expect("Failed to get current executor.");
234 let mut child_process = process::Command::new(path_to_self);
235 setup_common(&mut child_process, token);
236
237 Process::Unsandboxed(
238 child_process
239 .spawn()
240 .expect("Failed to start unsandboxed child process!"),
241 )
242 };
243
244 let (_receiver, sender) = server.accept().expect("Server failed to accept.");
245 sender.send(content)?;
246
247 Ok(process)
248}
249
250#[cfg(target_os = "ios")]
251pub fn spawn_multiprocess(_content: UnprivilegedContent) -> Result<Process, Error> {
252 log::error!("Multiprocess is not supported on iOS.");
253 process::exit(1);
254}
255
256fn setup_common<C: CommandMethods>(command: &mut C, token: String) {
257 C::arg(command, "--content-process");
258 C::arg(command, token);
259
260 if let Ok(value) = env::var("RUST_BACKTRACE") {
261 C::env(command, "RUST_BACKTRACE", value);
262 }
263
264 if let Ok(value) = env::var("RUST_LOG") {
265 C::env(command, "RUST_LOG", value);
266 }
267}
268
269trait CommandMethods {
271 fn arg<T>(&mut self, arg: T)
273 where
274 T: AsRef<OsStr>;
275
276 fn env<T, U>(&mut self, key: T, val: U)
278 where
279 T: AsRef<OsStr>,
280 U: AsRef<OsStr>;
281}
282
283impl CommandMethods for process::Command {
284 fn arg<T>(&mut self, arg: T)
285 where
286 T: AsRef<OsStr>,
287 {
288 self.arg(arg);
289 }
290
291 fn env<T, U>(&mut self, key: T, val: U)
292 where
293 T: AsRef<OsStr>,
294 U: AsRef<OsStr>,
295 {
296 self.env(key, val);
297 }
298}