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    )
18))]
19use gaol::profile::{Operation, PathPattern, Profile};
20use ipc_channel::Error;
21use serde::{Deserialize, Serialize};
22use servo_config::opts::Opts;
23use servo_config::prefs::Preferences;
24
25use crate::event_loop::NewScriptEventLoopProcessInfo;
26use crate::process_manager::Process;
27use crate::serviceworker::ServiceWorkerUnprivilegedContent;
28
29#[derive(Deserialize, Serialize)]
30#[allow(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))]
104pub fn content_process_sandbox_profile() -> Profile {
105    use std::path::PathBuf;
106
107    use embedder_traits::resources;
108
109    let mut operations = vec![Operation::FileReadAll(PathPattern::Literal(PathBuf::from(
110        "/dev/urandom",
111    )))];
112
113    operations.extend(
114        resources::sandbox_access_files()
115            .into_iter()
116            .map(|p| Operation::FileReadAll(PathPattern::Literal(p))),
117    );
118    operations.extend(
119        resources::sandbox_access_files_dirs()
120            .into_iter()
121            .map(|p| Operation::FileReadAll(PathPattern::Subpath(p))),
122    );
123
124    Profile::new(operations).expect("Failed to create sandbox profile!")
125}
126
127#[cfg(any(
128    target_os = "windows",
129    target_os = "ios",
130    target_os = "android",
131    target_env = "ohos",
132    target_arch = "arm",
133
134    // exclude apple arm devices
135    all(target_arch = "aarch64", not(target_os = "macos"))
136))]
137pub fn content_process_sandbox_profile() {
138    log::error!("Sandboxed multiprocess is not supported on this platform.");
139    process::exit(1);
140}
141
142#[cfg(any(
143    target_os = "windows",
144    target_os = "android",
145    target_env = "ohos",
146    target_arch = "arm",
147    target_arch = "aarch64"
148))]
149pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<Process, Error> {
150    use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
151    // Note that this function can panic, due to process creation,
152    // avoiding this panic would require a mechanism for dealing
153    // with low-resource scenarios.
154    let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
155        .expect("Failed to create IPC one-shot server.");
156
157    let path_to_self = env::current_exe().expect("Failed to get current executor.");
158    let mut child_process = process::Command::new(path_to_self);
159    setup_common(&mut child_process, token);
160
161    #[allow(clippy::zombie_processes)]
162    let child = child_process
163        .spawn()
164        .expect("Failed to start unsandboxed child process!");
165
166    let (_receiver, sender) = server.accept().expect("Server failed to accept.");
167    sender.send(content)?;
168
169    Ok(Process::Unsandboxed(child))
170}
171
172#[cfg(all(
173    not(target_os = "windows"),
174    not(target_os = "ios"),
175    not(target_os = "android"),
176    not(target_env = "ohos"),
177    not(target_arch = "arm"),
178    not(target_arch = "aarch64")
179))]
180pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<Process, Error> {
181    use gaol::sandbox::{self, Sandbox, SandboxMethods};
182    use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
183
184    // TODO: Move this impl out of the function. It is only currently here to avoid
185    // duplicating the feature flagging.
186    #[allow(non_local_definitions)]
187    impl CommandMethods for gaol::sandbox::Command {
188        fn arg<T>(&mut self, arg: T)
189        where
190            T: AsRef<OsStr>,
191        {
192            self.arg(arg);
193        }
194
195        fn env<T, U>(&mut self, key: T, val: U)
196        where
197            T: AsRef<OsStr>,
198            U: AsRef<OsStr>,
199        {
200            self.env(key, val);
201        }
202    }
203
204    // Note that this function can panic, due to process creation,
205    // avoiding this panic would require a mechanism for dealing
206    // with low-resource scenarios.
207    let (server, token) = IpcOneShotServer::<IpcSender<UnprivilegedContent>>::new()
208        .expect("Failed to create IPC one-shot server.");
209
210    // If there is a sandbox, use the `gaol` API to create the child process.
211    let process = if content.opts().sandbox {
212        let mut command = sandbox::Command::me().expect("Failed to get current sandbox.");
213        setup_common(&mut command, token);
214
215        let profile = content_process_sandbox_profile();
216        Process::Sandboxed(
217            Sandbox::new(profile)
218                .start(&mut command)
219                .expect("Failed to start sandboxed child process!")
220                .pid as u32,
221        )
222    } else {
223        let path_to_self = env::current_exe().expect("Failed to get current executor.");
224        let mut child_process = process::Command::new(path_to_self);
225        setup_common(&mut child_process, token);
226
227        Process::Unsandboxed(
228            child_process
229                .spawn()
230                .expect("Failed to start unsandboxed child process!"),
231        )
232    };
233
234    let (_receiver, sender) = server.accept().expect("Server failed to accept.");
235    sender.send(content)?;
236
237    Ok(process)
238}
239
240#[cfg(target_os = "ios")]
241pub fn spawn_multiprocess(_content: UnprivilegedContent) -> Result<Process, Error> {
242    log::error!("Multiprocess is not supported on iOS.");
243    process::exit(1);
244}
245
246fn setup_common<C: CommandMethods>(command: &mut C, token: String) {
247    C::arg(command, "--content-process");
248    C::arg(command, token);
249
250    if let Ok(value) = env::var("RUST_BACKTRACE") {
251        C::env(command, "RUST_BACKTRACE", value);
252    }
253
254    if let Ok(value) = env::var("RUST_LOG") {
255        C::env(command, "RUST_LOG", value);
256    }
257}
258
259/// A trait to unify commands launched as multiprocess with or without a sandbox.
260trait CommandMethods {
261    /// A command line argument.
262    fn arg<T>(&mut self, arg: T)
263    where
264        T: AsRef<OsStr>;
265
266    /// An environment variable.
267    fn env<T, U>(&mut self, key: T, val: U)
268    where
269        T: AsRef<OsStr>,
270        U: AsRef<OsStr>;
271}
272
273impl CommandMethods for process::Command {
274    fn arg<T>(&mut self, arg: T)
275    where
276        T: AsRef<OsStr>,
277    {
278        self.arg(arg);
279    }
280
281    fn env<T, U>(&mut self, key: T, val: U)
282    where
283        T: AsRef<OsStr>,
284        U: AsRef<OsStr>,
285    {
286        self.env(key, val);
287    }
288}