• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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