Skip to main content

servo_constellation/
sandboxing.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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/// Our content process sandbox profile on Mac. As restrictive as possible.
55#[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/// Our content process sandbox profile on Linux. As restrictive as possible.
97#[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    // exclude apple arm devices
141    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    // Note that this function can panic, due to process creation,
160    // avoiding this panic would require a mechanism for dealing
161    // with low-resource scenarios.
162    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    // TODO: Move this impl out of the function. It is only currently here to avoid
195    // duplicating the feature flagging.
196    #[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    // Note that this function can panic, due to process creation,
215    // avoiding this panic would require a mechanism for dealing
216    // with low-resource scenarios.
217    let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
218        .expect("Failed to create IPC one-shot server.");
219
220    // If there is a sandbox, use the `gaol` API to create the child process.
221    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
269/// A trait to unify commands launched as multiprocess with or without a sandbox.
270trait CommandMethods {
271    /// A command line argument.
272    fn arg<T>(&mut self, arg: T)
273    where
274        T: AsRef<OsStr>;
275
276    /// An environment variable.
277    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}