devices/virtio/vhost_user_backend/fs/sys/
linux.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::os::fd::OwnedFd;
6use std::os::unix::process::ExitStatusExt;
7use std::path::Path;
8use std::path::PathBuf;
9
10use anyhow::bail;
11use anyhow::Context;
12use base::error;
13use base::linux::max_open_files;
14use base::sys::wait_for_pid;
15use base::AsRawDescriptor;
16use base::AsRawDescriptors;
17use base::IntoRawDescriptor;
18use base::RawDescriptor;
19use base::UnixSeqpacketListener;
20use cros_async::Executor;
21use jail::create_base_minijail;
22use jail::create_base_minijail_without_pivot_root;
23use minijail::Minijail;
24
25use crate::virtio::vhost_user_backend::fs::FsBackend;
26use crate::virtio::vhost_user_backend::fs::Options;
27use crate::virtio::vhost_user_backend::handler::DeviceRequestHandler;
28use crate::virtio::vhost_user_backend::BackendConnection;
29
30fn default_uidmap() -> String {
31    // SAFETY: trivially safe
32    let euid = unsafe { libc::geteuid() };
33    format!("{euid} {euid} 1")
34}
35
36fn default_gidmap() -> String {
37    // SAFETY: trivially safe
38    let egid = unsafe { libc::getegid() };
39    format!("{egid} {egid} 1")
40}
41
42#[allow(clippy::unnecessary_cast)]
43fn jail_and_fork(
44    mut keep_rds: Vec<RawDescriptor>,
45    dir_path: PathBuf,
46    uid: u32,
47    gid: u32,
48    uid_map: Option<String>,
49    gid_map: Option<String>,
50    disable_sandbox: bool,
51    pivot_root: bool,
52) -> anyhow::Result<i32> {
53    let limit = max_open_files()
54        .context("failed to get max open files")?
55        .rlim_max;
56    // Create new minijail sandbox
57    let jail = if disable_sandbox {
58        if pivot_root {
59            create_base_minijail(dir_path.as_path(), limit)
60        } else {
61            create_base_minijail_without_pivot_root(dir_path.as_path(), limit)
62        }?
63    } else {
64        let mut j: Minijail = Minijail::new()?;
65
66        j.namespace_pids();
67        j.namespace_user();
68        j.namespace_user_disable_setgroups();
69        if uid != 0 {
70            j.change_uid(uid);
71        }
72        if gid != 0 {
73            j.change_gid(gid);
74        }
75        j.uidmap(&uid_map.unwrap_or_else(default_uidmap))?;
76        j.gidmap(&gid_map.unwrap_or_else(default_gidmap))?;
77        j.run_as_init();
78
79        j.namespace_vfs();
80        j.namespace_net();
81        j.no_new_privs();
82
83        // Only pivot_root if we are not re-using the current root directory.
84        if dir_path != Path::new("/") {
85            // It's safe to call `namespace_vfs` multiple times.
86            j.namespace_vfs();
87            j.enter_pivot_root(&dir_path)?;
88        }
89        j.set_remount_mode(libc::MS_SLAVE);
90
91        j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)?;
92        // vvu locks around 512k memory. Just give 1M.
93        j.set_rlimit(libc::RLIMIT_MEMLOCK as i32, 1 << 20, 1 << 20)?;
94        #[cfg(not(feature = "seccomp_trace"))]
95        jail::set_embedded_bpf_program(&mut j, "fs_device_vhost_user")?;
96        j.use_seccomp_filter();
97        j
98    };
99
100    // Make sure there are no duplicates in keep_rds
101    keep_rds.sort_unstable();
102    keep_rds.dedup();
103
104    // fork on the jail here
105    // SAFETY: trivially safe
106    let pid = unsafe { jail.fork(Some(&keep_rds))? };
107
108    if pid > 0 {
109        // Current FS driver jail does not use seccomp and jail_and_fork() does not have other
110        // users, so we do nothing here for seccomp_trace
111        // SAFETY: trivially safe
112        unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
113    }
114
115    if pid < 0 {
116        bail!("Fork error! {}", std::io::Error::last_os_error());
117    }
118
119    Ok(pid)
120}
121
122/// Starts a vhost-user fs device.
123/// Returns an error if the given `args` is invalid or the device fails to run.
124#[allow(unused_mut)]
125pub fn start_device(mut opts: Options) -> anyhow::Result<()> {
126    #[allow(unused_mut)]
127    let mut is_pivot_root_required = true;
128    #[cfg(feature = "fs_runtime_ugid_map")]
129    if let Some(ref mut cfg) = opts.cfg {
130        if !cfg.ugid_map.is_empty() && (!opts.disable_sandbox || !opts.skip_pivot_root) {
131            bail!("uid_gid_map can only be set with disable sandbox and skip_pivot_root option");
132        }
133
134        if cfg.unmap_guest_memory_on_fork && !opts.disable_sandbox {
135            bail!("unmap_guest_memory_on_fork requires --disable-sandbox");
136        }
137
138        if opts.skip_pivot_root {
139            is_pivot_root_required = false;
140        }
141    }
142    let ex = Executor::new().context("Failed to create executor")?;
143
144    let mut allowlist_listener_fd = None;
145    if let Some(ref path) = opts.allowlist_socket_path {
146        let listener =
147            UnixSeqpacketListener::bind(path).context("failed to bind allowlist socket")?;
148        let fd = OwnedFd::from(listener).into_raw_descriptor();
149        allowlist_listener_fd = Some(fd);
150    }
151
152    let mut fs_device = FsBackend::new(
153        &opts.tag,
154        opts.shared_dir
155            .to_str()
156            .expect("Failed to convert opts.shared_dir to str()"),
157        opts.skip_pivot_root,
158        opts.cfg,
159        allowlist_listener_fd,
160    )?;
161
162    let mut keep_rds = fs_device.keep_rds.clone();
163    keep_rds.append(&mut ex.as_raw_descriptors());
164
165    let conn =
166        BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;
167    keep_rds.push(conn.as_raw_descriptor());
168    if let Some(fd) = allowlist_listener_fd {
169        keep_rds.push(fd);
170    }
171
172    base::syslog::push_descriptors(&mut keep_rds);
173    cros_tracing::push_descriptors!(&mut keep_rds);
174    metrics::push_descriptors(&mut keep_rds);
175    let pid = jail_and_fork(
176        keep_rds,
177        opts.shared_dir,
178        opts.uid,
179        opts.gid,
180        opts.uid_map,
181        opts.gid_map,
182        opts.disable_sandbox,
183        is_pivot_root_required,
184    )?;
185
186    match pid {
187        0 => {
188            // Child process runs the device and exits, not returns.
189            fs_device.start_allowlist_listener();
190            // `PassthroughFs` and `GuestMemory` destructors are slow (b/440937769), so call
191            // `exit()` before dropping `DeviceRequestHandler` to ensure the device and its
192            // workers aren't stopped.
193            let mut handler = DeviceRequestHandler::new(fs_device);
194            if let Err(e) = ex.run_until(conn.run_req_handler(&mut handler, &ex)) {
195                error!("Error in vhost-user-fs device: {:#}", e);
196                std::process::exit(1);
197            }
198            std::process::exit(0);
199        }
200        pid if pid < 0 => {
201            unreachable!("fork error must have been handled in jail_and_fork()");
202        }
203        _ => {
204            // fs_device is not needed in the parent process.
205            drop(fs_device);
206
207            let (_child_pid, status) =
208                wait_for_pid(pid, 0).context("failed to wait for child process")?;
209            if let Some(signal) = status.signal() {
210                panic!("Child process {pid} was killed by signal {signal}");
211            }
212            if let Some(exit_code) = status.code() {
213                if exit_code != 0 {
214                    bail!("Child process {pid} exited with code {exit_code}");
215                }
216            }
217        }
218    };
219    Ok(())
220}