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