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