1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::path::{Path, PathBuf};
6 use std::str;
7
8 use libc::{self, c_ulong, gid_t, uid_t};
9
10 use anyhow::{bail, Context, Result};
11 use base::*;
12 use minijail::{self, Minijail};
13
14 use crate::JailConfig;
15
16 pub(super) struct SandboxConfig<'a> {
17 pub(super) limit_caps: bool,
18 pub(super) log_failures: bool,
19 pub(super) seccomp_policy: &'a Path,
20 pub(super) uid_map: Option<&'a str>,
21 pub(super) gid_map: Option<&'a str>,
22 pub(super) remount_mode: Option<c_ulong>,
23 }
24
25 pub(crate) struct ScopedMinijail(pub Minijail);
26
27 impl Drop for ScopedMinijail {
drop(&mut self)28 fn drop(&mut self) {
29 let _ = self.0.kill();
30 }
31 }
32
create_base_minijail( root: &Path, r_limit: Option<u64>, config: Option<&SandboxConfig>, ) -> Result<Minijail>33 pub(super) fn create_base_minijail(
34 root: &Path,
35 r_limit: Option<u64>,
36 config: Option<&SandboxConfig>,
37 ) -> Result<Minijail> {
38 // All child jails run in a new user namespace without any users mapped,
39 // they run as nobody unless otherwise configured.
40 let mut j = Minijail::new().context("failed to jail device")?;
41
42 if let Some(config) = config {
43 j.namespace_pids();
44 j.namespace_user();
45 j.namespace_user_disable_setgroups();
46 if config.limit_caps {
47 // Don't need any capabilities.
48 j.use_caps(0);
49 }
50 if let Some(uid_map) = config.uid_map {
51 j.uidmap(uid_map).context("error setting UID map")?;
52 }
53 if let Some(gid_map) = config.gid_map {
54 j.gidmap(gid_map).context("error setting GID map")?;
55 }
56 // Run in a new mount namespace.
57 j.namespace_vfs();
58
59 // Run in an empty network namespace.
60 j.namespace_net();
61
62 // Don't allow the device to gain new privileges.
63 j.no_new_privs();
64
65 // By default we'll prioritize using the pre-compiled .bpf over the .policy
66 // file (the .bpf is expected to be compiled using "trap" as the failure
67 // behavior instead of the default "kill" behavior).
68 // Refer to the code comment for the "seccomp-log-failures"
69 // command-line parameter for an explanation about why the |log_failures|
70 // flag forces the use of .policy files (and the build-time alternative to
71 // this run-time flag).
72 let bpf_policy_file = config.seccomp_policy.with_extension("bpf");
73 if bpf_policy_file.exists() && !config.log_failures {
74 j.parse_seccomp_program(&bpf_policy_file)
75 .context("failed to parse precompiled seccomp policy")?;
76 } else {
77 // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP,
78 // which will correctly kill the entire device process if a worker
79 // thread commits a seccomp violation.
80 j.set_seccomp_filter_tsync();
81 if config.log_failures {
82 j.log_seccomp_filter_failures();
83 }
84 j.parse_seccomp_filters(&config.seccomp_policy.with_extension("policy"))
85 .context("failed to parse seccomp policy")?;
86 }
87 j.use_seccomp_filter();
88 // Don't do init setup.
89 j.run_as_init();
90 // Set up requested remount mode instead of default MS_PRIVATE.
91 if let Some(mode) = config.remount_mode {
92 j.set_remount_mode(mode);
93 }
94 }
95
96 // Only pivot_root if we are not re-using the current root directory.
97 if root != Path::new("/") {
98 // It's safe to call `namespace_vfs` multiple times.
99 j.namespace_vfs();
100 j.enter_pivot_root(root)
101 .context("failed to pivot root device")?;
102 }
103
104 // Most devices don't need to open many fds.
105 let limit = if let Some(r) = r_limit { r } else { 1024u64 };
106 j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)
107 .context("error setting max open files")?;
108
109 Ok(j)
110 }
111
simple_jail( jail_config: &Option<JailConfig>, policy: &str, ) -> Result<Option<Minijail>>112 pub(super) fn simple_jail(
113 jail_config: &Option<JailConfig>,
114 policy: &str,
115 ) -> Result<Option<Minijail>> {
116 if let Some(jail_config) = jail_config {
117 // A directory for a jailed device's pivot root.
118 if !jail_config.pivot_root.exists() {
119 bail!(
120 "{:?} doesn't exist, can't jail devices",
121 jail_config.pivot_root
122 );
123 }
124 let policy_path: PathBuf = jail_config.seccomp_policy_dir.join(policy);
125 let config = SandboxConfig {
126 limit_caps: true,
127 log_failures: jail_config.seccomp_log_failures,
128 seccomp_policy: &policy_path,
129 uid_map: None,
130 gid_map: None,
131 remount_mode: None,
132 };
133 Ok(Some(create_base_minijail(
134 &jail_config.pivot_root,
135 None,
136 Some(&config),
137 )?))
138 } else {
139 Ok(None)
140 }
141 }
142
143 /// Mirror-mount all the directories in `dirs` into `jail` on a best-effort basis.
144 ///
145 /// This function will not return an error if any of the directories in `dirs` is missing.
146 #[cfg(any(feature = "gpu", feature = "video-decoder", feature = "video-encoder"))]
jail_mount_bind_if_exists<P: AsRef<std::ffi::OsStr>>( jail: &mut Minijail, dirs: &[P], ) -> Result<()>147 pub(super) fn jail_mount_bind_if_exists<P: AsRef<std::ffi::OsStr>>(
148 jail: &mut Minijail,
149 dirs: &[P],
150 ) -> Result<()> {
151 for dir in dirs {
152 let dir_path = Path::new(dir);
153 if dir_path.exists() {
154 jail.mount_bind(dir_path, dir_path, false)?;
155 }
156 }
157
158 Ok(())
159 }
160
161 #[derive(Copy, Clone)]
162 #[cfg_attr(not(feature = "tpm"), allow(dead_code))]
163 pub(super) struct Ids {
164 pub(super) uid: uid_t,
165 pub(super) gid: gid_t,
166 }
167
168 #[cfg(feature = "gpu")]
add_current_user_as_root_to_jail(jail: &mut Minijail) -> Result<Ids>169 pub(super) fn add_current_user_as_root_to_jail(jail: &mut Minijail) -> Result<Ids> {
170 let crosvm_uid = geteuid();
171 let crosvm_gid = getegid();
172 jail.uidmap(&format!("0 {0} 1", crosvm_uid))
173 .context("error setting UID map")?;
174 jail.gidmap(&format!("0 {0} 1", crosvm_gid))
175 .context("error setting GID map")?;
176
177 Ok(Ids {
178 uid: crosvm_uid,
179 gid: crosvm_gid,
180 })
181 }
182
183 /// Set the uid/gid for the jailed process and give a basic id map. This is
184 /// required for bind mounts to work.
add_current_user_to_jail(jail: &mut Minijail) -> Result<Ids>185 pub(super) fn add_current_user_to_jail(jail: &mut Minijail) -> Result<Ids> {
186 let crosvm_uid = geteuid();
187 let crosvm_gid = getegid();
188
189 jail.uidmap(&format!("{0} {0} 1", crosvm_uid))
190 .context("error setting UID map")?;
191 jail.gidmap(&format!("{0} {0} 1", crosvm_gid))
192 .context("error setting GID map")?;
193
194 if crosvm_uid != 0 {
195 jail.change_uid(crosvm_uid);
196 }
197 if crosvm_gid != 0 {
198 jail.change_gid(crosvm_gid);
199 }
200
201 Ok(Ids {
202 uid: crosvm_uid,
203 gid: crosvm_gid,
204 })
205 }
206