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 serde::{Deserialize, Serialize};
23use servo_config::opts::Opts;
24use servo_config::prefs::Preferences;
25
26use crate::event_loop::NewScriptEventLoopProcessInfo;
27use crate::serviceworker::ServiceWorkerUnprivilegedContent;
28
29#[derive(Deserialize, Serialize)]
30#[expect(clippy::large_enum_variant)]
31pub enum UnprivilegedContent {
32    ScriptEventLoop(NewScriptEventLoopProcessInfo),
33    ServiceWorker(ServiceWorkerUnprivilegedContent),
34}
35
36impl UnprivilegedContent {
37    pub fn opts(&self) -> Opts {
38        match self {
39            UnprivilegedContent::ScriptEventLoop(content) => content.opts.clone(),
40            UnprivilegedContent::ServiceWorker(content) => content.opts.clone(),
41        }
42    }
43
44    pub fn prefs(&self) -> &Preferences {
45        match self {
46            UnprivilegedContent::ScriptEventLoop(content) => &content.prefs,
47            UnprivilegedContent::ServiceWorker(content) => &content.prefs,
48        }
49    }
50}
51
52/// Our content process sandbox profile on Mac. As restrictive as possible.
53#[cfg(target_os = "macos")]
54pub fn content_process_sandbox_profile() -> Profile {
55    use std::path::PathBuf;
56
57    use embedder_traits::resources;
58    use gaol::platform;
59
60    let mut operations = vec![
61        Operation::FileReadAll(PathPattern::Literal(PathBuf::from("/dev/urandom"))),
62        Operation::FileReadAll(PathPattern::Subpath(PathBuf::from("/Library/Fonts"))),
63        Operation::FileReadAll(PathPattern::Subpath(PathBuf::from("/System/Library/Fonts"))),
64        Operation::FileReadAll(PathPattern::Subpath(PathBuf::from(
65            "/System/Library/Frameworks/ApplicationServices.framework",
66        ))),
67        Operation::FileReadAll(PathPattern::Subpath(PathBuf::from(
68            "/System/Library/Frameworks/CoreGraphics.framework",
69        ))),
70        Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/"))),
71        Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/Library"))),
72        Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/System"))),
73        Operation::FileReadMetadata(PathPattern::Literal(PathBuf::from("/etc"))),
74        Operation::SystemInfoRead,
75        Operation::PlatformSpecific(platform::macos::Operation::MachLookup(
76            b"com.apple.FontServer".to_vec(),
77        )),
78    ];
79
80    operations.extend(
81        resources::sandbox_access_files()
82            .into_iter()
83            .map(|p| Operation::FileReadAll(PathPattern::Literal(p))),
84    );
85    operations.extend(
86        resources::sandbox_access_files_dirs()
87            .into_iter()
88            .map(|p| Operation::FileReadAll(PathPattern::Subpath(p))),
89    );
90
91    Profile::new(operations).expect("Failed to create sandbox profile!")
92}
93
94/// Our content process sandbox profile on Linux. As restrictive as possible.
95#[cfg(all(
96    not(target_os = "macos"),
97    not(target_os = "windows"),
98    not(target_os = "ios"),
99    not(target_os = "android"),
100    not(target_env = "ohos"),
101    not(target_arch = "arm"),
102    not(target_arch = "aarch64"),
103    not(target_arch = "riscv32"),
104    not(target_arch = "riscv64")
105))]
106pub fn content_process_sandbox_profile() -> Profile {
107    use std::path::PathBuf;
108
109    use embedder_traits::resources;
110
111    let mut operations = vec![Operation::FileReadAll(PathPattern::Literal(PathBuf::from(
112        "/dev/urandom",
113    )))];
114
115    operations.extend(
116        resources::sandbox_access_files()
117            .into_iter()
118            .map(|p| Operation::FileReadAll(PathPattern::Literal(p))),
119    );
120    operations.extend(
121        resources::sandbox_access_files_dirs()
122            .into_iter()
123            .map(|p| Operation::FileReadAll(PathPattern::Subpath(p))),
124    );
125
126    Profile::new(operations).expect("Failed to create sandbox profile!")
127}
128
129#[cfg(any(
130    target_os = "windows",
131    target_os = "ios",
132    target_os = "android",
133    target_env = "ohos",
134    target_arch = "arm",
135    target_arch = "riscv32",
136    target_arch = "riscv64",
137
138    // exclude apple arm devices
139    all(target_arch = "aarch64", not(target_os = "macos"))
140))]
141pub fn content_process_sandbox_profile() {
142    log::error!("Sandboxed multiprocess is not supported on this platform.");
143    process::exit(1);
144}
145
146#[cfg(any(
147    target_os = "windows",
148    target_os = "android",
149    target_env = "ohos",
150    target_arch = "arm",
151    target_arch = "aarch64",
152    target_arch = "riscv32",
153    target_arch = "riscv64"
154))]
155pub fn spawn_multiprocess(
156    content: UnprivilegedContent,
157) -> Result<crate::process_manager::Process, ipc_channel::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(crate::process_manager::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(
191    content: UnprivilegedContent,
192) -> Result<crate::process_manager::Process, ipc_channel::IpcError> {
193    use gaol::sandbox::{self, Sandbox, SandboxMethods};
194    use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
195
196    // TODO: Move this impl out of the function. It is only currently here to avoid
197    // duplicating the feature flagging.
198    #[allow(non_local_definitions)]
199    impl CommandMethods for gaol::sandbox::Command {
200        fn arg<T>(&mut self, arg: T)
201        where
202            T: AsRef<OsStr>,
203        {
204            self.arg(arg);
205        }
206
207        fn env<T, U>(&mut self, key: T, val: U)
208        where
209            T: AsRef<OsStr>,
210            U: AsRef<OsStr>,
211        {
212            self.env(key, val);
213        }
214    }
215
216    // Note that this function can panic, due to process creation,
217    // avoiding this panic would require a mechanism for dealing
218    // with low-resource scenarios.
219    let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
220        .expect("Failed to create IPC one-shot server.");
221
222    // If there is a sandbox, use the `gaol` API to create the child process.
223    let process = if content.opts().sandbox {
224        let mut command = sandbox::Command::me().expect("Failed to get current sandbox.");
225        setup_common(&mut command, token);
226
227        let profile = content_process_sandbox_profile();
228        crate::process_manager::Process::Sandboxed(
229            Sandbox::new(profile)
230                .start(&mut command)
231                .expect("Failed to start sandboxed child process!")
232                .pid as u32,
233        )
234    } else {
235        let path_to_self = env::current_exe().expect("Failed to get current executor.");
236        let mut child_process = process::Command::new(path_to_self);
237        setup_common(&mut child_process, token);
238
239        crate::process_manager::Process::Unsandboxed(
240            child_process
241                .spawn()
242                .expect("Failed to start unsandboxed child process!"),
243        )
244    };
245
246    let (_receiver, sender) = server.accept().expect("Server failed to accept.");
247    sender.send(content)?;
248
249    Ok(process)
250}
251
252#[cfg(target_os = "ios")]
253pub fn spawn_multiprocess(_content: UnprivilegedContent) -> Result<Process, Error> {
254    log::error!("Multiprocess is not supported on iOS.");
255    process::exit(1);
256}
257
258fn setup_common<C: CommandMethods>(command: &mut C, token: String) {
259    C::arg(command, "--content-process");
260    C::arg(command, token);
261
262    if let Ok(value) = env::var("RUST_BACKTRACE") {
263        C::env(command, "RUST_BACKTRACE", value);
264    }
265
266    if let Ok(value) = env::var("RUST_LOG") {
267        C::env(command, "RUST_LOG", value);
268    }
269}
270
271/// A trait to unify commands launched as multiprocess with or without a sandbox.
272trait CommandMethods {
273    /// A command line argument.
274    fn arg<T>(&mut self, arg: T)
275    where
276        T: AsRef<OsStr>;
277
278    /// An environment variable.
279    fn env<T, U>(&mut self, key: T, val: U)
280    where
281        T: AsRef<OsStr>,
282        U: AsRef<OsStr>;
283}
284
285impl CommandMethods for process::Command {
286    fn arg<T>(&mut self, arg: T)
287    where
288        T: AsRef<OsStr>,
289    {
290        self.arg(arg);
291    }
292
293    fn env<T, U>(&mut self, key: T, val: U)
294    where
295        T: AsRef<OsStr>,
296        U: AsRef<OsStr>,
297    {
298        self.env(key, val);
299    }
300}