devices/virtio/vhost_user_backend/
fs.rs

1// Copyright 2021 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
5mod sys;
6
7use std::collections::BTreeMap;
8use std::path::PathBuf;
9use std::sync::Arc;
10
11use anyhow::bail;
12use argh::FromArgs;
13use base::error;
14use base::info;
15use base::warn;
16use base::RawDescriptor;
17use base::Tube;
18use base::WorkerThread;
19use data_model::Le32;
20use fuse::Server;
21use hypervisor::ProtectionType;
22use snapshot::AnySnapshot;
23use sync::Mutex;
24pub use sys::start_device as run_fs_device;
25use virtio_sys::virtio_fs::virtio_fs_config;
26use vm_memory::GuestMemory;
27use vmm_vhost::message::VhostUserProtocolFeatures;
28use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
29use zerocopy::IntoBytes;
30
31use crate::virtio;
32use crate::virtio::copy_config;
33use crate::virtio::device_constants::fs::FS_MAX_TAG_LEN;
34use crate::virtio::fs::passthrough::PassthroughFs;
35use crate::virtio::fs::Config;
36use crate::virtio::fs::Worker;
37use crate::virtio::vhost_user_backend::handler::Error as DeviceError;
38use crate::virtio::vhost_user_backend::handler::VhostUserDevice;
39use crate::virtio::Queue;
40
41const MAX_QUEUE_NUM: usize = 2; /* worker queue and high priority queue */
42
43struct FsBackend {
44    server: Arc<fuse::Server<PassthroughFs>>,
45    tag: String,
46    avail_features: u64,
47    workers: BTreeMap<usize, WorkerThread<Queue>>,
48    keep_rds: Vec<RawDescriptor>,
49    unmap_guest_memory_on_fork: bool,
50}
51
52impl FsBackend {
53    #[allow(unused_variables)]
54    pub fn new(
55        tag: &str,
56        shared_dir: &str,
57        skip_pivot_root: bool,
58        cfg: Option<Config>,
59    ) -> anyhow::Result<Self> {
60        if tag.len() > FS_MAX_TAG_LEN {
61            bail!(
62                "fs tag is too long: {} (max supported: {})",
63                tag.len(),
64                FS_MAX_TAG_LEN
65            );
66        }
67
68        let avail_features = virtio::base_features(ProtectionType::Unprotected)
69            | 1 << VHOST_USER_F_PROTOCOL_FEATURES;
70
71        let cfg = cfg.unwrap_or_default();
72
73        #[cfg(any(target_os = "android", target_os = "linux"))]
74        let unmap_guest_memory_on_fork = cfg.unmap_guest_memory_on_fork;
75        #[cfg(not(any(target_os = "android", target_os = "linux")))]
76        let unmap_guest_memory_on_fork = false;
77
78        // Use default passthroughfs config
79        #[allow(unused_mut)]
80        let mut fs = PassthroughFs::new(tag, cfg)?;
81        #[cfg(feature = "fs_runtime_ugid_map")]
82        if skip_pivot_root {
83            fs.set_root_dir(shared_dir.to_string())?;
84        }
85
86        let mut keep_rds: Vec<RawDescriptor> = [0, 1, 2].to_vec();
87        keep_rds.append(&mut fs.keep_rds());
88
89        let server = Arc::new(Server::new(fs));
90
91        Ok(FsBackend {
92            server,
93            tag: tag.to_owned(),
94            avail_features,
95            workers: Default::default(),
96            keep_rds,
97            unmap_guest_memory_on_fork,
98        })
99    }
100}
101
102impl VhostUserDevice for FsBackend {
103    fn max_queue_num(&self) -> usize {
104        MAX_QUEUE_NUM
105    }
106
107    fn features(&self) -> u64 {
108        self.avail_features
109    }
110
111    fn protocol_features(&self) -> VhostUserProtocolFeatures {
112        VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
113    }
114
115    fn read_config(&self, offset: u64, data: &mut [u8]) {
116        let mut config = virtio_fs_config {
117            tag: [0; FS_MAX_TAG_LEN],
118            num_request_queues: Le32::from(1),
119        };
120        config.tag[..self.tag.len()].copy_from_slice(self.tag.as_bytes());
121        copy_config(data, 0, config.as_bytes(), offset);
122    }
123
124    fn reset(&mut self) {
125        for worker in std::mem::take(&mut self.workers).into_values() {
126            let _ = worker.stop();
127        }
128    }
129
130    fn start_queue(
131        &mut self,
132        idx: usize,
133        queue: virtio::Queue,
134        _mem: GuestMemory,
135    ) -> anyhow::Result<()> {
136        if self.workers.contains_key(&idx) {
137            warn!("Starting new queue handler without stopping old handler");
138            self.stop_queue(idx)?;
139        }
140
141        let (_, fs_device_tube) = Tube::pair()?;
142        let tube = Arc::new(Mutex::new(fs_device_tube));
143
144        let server = self.server.clone();
145
146        // Slot is always going to be 0 because we do not support DAX
147        let slot: u32 = 0;
148
149        let worker = WorkerThread::start(format!("v_fs:{}:{}", self.tag, idx), move |kill_evt| {
150            let mut worker = Worker::new(queue, server, tube, slot);
151            if let Err(e) = worker.run(kill_evt) {
152                error!("vhost-user-fs worker failed: {e:#}");
153            }
154            worker.queue
155        });
156        self.workers.insert(idx, worker);
157
158        Ok(())
159    }
160
161    fn stop_queue(&mut self, idx: usize) -> anyhow::Result<virtio::Queue> {
162        // TODO(b/440937769): Remove debug logs once the issue is resolved.
163        info!("Stopping vhost-user fs queue [{idx}]");
164        if let Some(worker) = self.workers.remove(&idx) {
165            let queue = worker.stop();
166            Ok(queue)
167        } else {
168            Err(anyhow::Error::new(DeviceError::WorkerNotFound))
169        }
170    }
171
172    fn unmap_guest_memory_on_fork(&self) -> bool {
173        self.unmap_guest_memory_on_fork
174    }
175
176    fn enter_suspended_state(&mut self) -> anyhow::Result<()> {
177        // No non-queue workers.
178        Ok(())
179    }
180
181    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
182        bail!("snapshot not implemented for vhost-user fs");
183    }
184
185    fn restore(&mut self, _data: AnySnapshot) -> anyhow::Result<()> {
186        bail!("snapshot not implemented for vhost-user fs");
187    }
188}
189
190#[derive(FromArgs)]
191#[argh(subcommand, name = "fs")]
192/// FS Device
193pub struct Options {
194    #[argh(option, arg_name = "PATH", hidden_help)]
195    /// deprecated - please use --socket-path instead
196    socket: Option<String>,
197    #[argh(option, arg_name = "PATH")]
198    /// path to the vhost-user socket to bind to.
199    /// If this flag is set, --fd cannot be specified.
200    socket_path: Option<String>,
201    #[argh(option, arg_name = "FD")]
202    /// file descriptor of a connected vhost-user socket.
203    /// If this flag is set, --socket-path cannot be specified.
204    fd: Option<RawDescriptor>,
205
206    #[argh(option, arg_name = "TAG")]
207    /// the virtio-fs tag
208    tag: String,
209    #[argh(option, arg_name = "DIR")]
210    /// path to a directory to share
211    shared_dir: PathBuf,
212    #[argh(option, arg_name = "UIDMAP")]
213    /// uid map to use
214    uid_map: Option<String>,
215    #[argh(option, arg_name = "GIDMAP")]
216    /// gid map to use
217    gid_map: Option<String>,
218    #[argh(option, arg_name = "CFG")]
219    /// colon-separated options for configuring a directory to be
220    /// shared with the VM through virtio-fs. The format is the same as
221    /// `crosvm run --shared-dir` flag except only the keys related to virtio-fs
222    /// are valid here.
223    cfg: Option<Config>,
224    #[argh(option, arg_name = "UID", default = "0")]
225    /// uid of the device process in the new user namespace created by minijail.
226    /// These two options (uid/gid) are useful when the crosvm process cannot
227    /// get CAP_SETGID/CAP_SETUID but an identity mapping of the current
228    /// user/group between the VM and the host is required.
229    /// Say the current user and the crosvm process has uid 5000, a user can use
230    /// "uid=5000" and "uidmap=5000 5000 1" such that files owned by user 5000
231    /// still appear to be owned by user 5000 in the VM. These 2 options are
232    /// useful only when there is 1 user in the VM accessing shared files.
233    /// If multiple users want to access the shared file, gid/uid options are
234    /// useless. It'd be better to create a new user namespace and give
235    /// CAP_SETUID/CAP_SETGID to the crosvm.
236    /// Default: 0.
237    uid: u32,
238    #[argh(option, arg_name = "GID", default = "0")]
239    /// gid of the device process in the new user namespace created by minijail.
240    /// Default: 0.
241    gid: u32,
242    #[argh(switch)]
243    /// disable-sandbox controls whether vhost-user-fs device uses minijail sandbox.
244    /// By default, it is false, the vhost-user-fs will enter new mnt/user/pid/net
245    /// namespace. If the this option is true, the vhost-user-fs device only create
246    /// a new mount namespace and run without seccomp filter.
247    /// Default: false.
248    disable_sandbox: bool,
249    #[argh(option, arg_name = "skip_pivot_root", default = "false")]
250    /// disable pivot_root when process is jailed.
251    ///
252    /// virtio-fs typically uses mount namespaces and pivot_root for file system isolation,
253    /// making the jailed process's root directory "/".
254    ///
255    /// Android's security model restricts crosvm's access to certain system capabilities,
256    /// specifically those related to managing mount namespaces and using pivot_root.
257    /// These capabilities are typically associated with the SYS_ADMIN capability.
258    /// To maintain a secure environment, Android relies on mechanisms like SELinux to
259    /// enforce isolation and control access to directories.
260    #[allow(dead_code)]
261    skip_pivot_root: bool,
262}