devices/virtio/vhost_user_backend/
fs.rs1mod sys;
6
7use std::collections::BTreeMap;
8use std::path::PathBuf;
9use std::sync::Arc;
10use std::sync::RwLock;
11
12use anyhow::bail;
13use argh::FromArgs;
14use base::error;
15use base::info;
16use base::warn;
17use base::AsRawDescriptor;
18use base::FromRawDescriptor;
19use base::IntoRawDescriptor;
20use base::RawDescriptor;
21use base::SafeDescriptor;
22use base::Tube;
23use base::UnixSeqpacketListener;
24use base::WorkerThread;
25use data_model::Le32;
26use fuse::Server;
27use hypervisor::ProtectionType;
28use snapshot::AnySnapshot;
29use sync::Mutex;
30pub use sys::start_device as run_fs_device;
31use virtio_sys::virtio_fs::virtio_fs_config;
32use vm_control::FsAllowlistCommand;
33use vm_control::FsAllowlistResponse;
34use vm_memory::GuestMemory;
35use vmm_vhost::message::VhostUserProtocolFeatures;
36use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
37use zerocopy::IntoBytes;
38
39use crate::virtio;
40use crate::virtio::copy_config;
41use crate::virtio::device_constants::fs::FS_MAX_TAG_LEN;
42use crate::virtio::fs::passthrough::PassthroughFs;
43use crate::virtio::fs::Config;
44use crate::virtio::fs::PathAllowlist;
45use crate::virtio::fs::Worker;
46use crate::virtio::vhost_user_backend::handler::Error as DeviceError;
47use crate::virtio::vhost_user_backend::handler::VhostUserDevice;
48use crate::virtio::Queue;
49
50const MAX_QUEUE_NUM: usize = 2; pub(crate) struct FsBackend {
53 server: Arc<fuse::Server<PassthroughFs>>,
54 tag: String,
55 avail_features: u64,
56 workers: BTreeMap<usize, WorkerThread<Queue>>,
57 keep_rds: Vec<RawDescriptor>,
58 unmap_guest_memory_on_fork: bool,
59 allowlist_socket_fd: Option<SafeDescriptor>,
60 allowlist: Option<Arc<RwLock<PathAllowlist>>>,
61}
62
63fn handle_client_session(tube: Tube, allowlist: &Arc<RwLock<PathAllowlist>>) {
77 loop {
78 match tube.recv::<FsAllowlistCommand>() {
79 Ok(cmd) => {
80 let result = match cmd {
81 FsAllowlistCommand::AddPaths { paths } => {
82 info!("Allowlist socket: Add paths {:?}", paths);
83 let mut al_guard = allowlist.write().expect(
84 "Allowlist lock poisoned during write (add_paths). Terminating.",
85 );
86 let mut al_clone = al_guard.clone();
87 let mut success = true;
88 for path in &paths {
89 if !al_clone.add_path(path) {
90 error!("Allowlist socket: Failed to add invalid path: {:?}", path);
91 success = false;
92 break;
93 }
94 }
95 if success {
96 *al_guard = al_clone;
97 FsAllowlistResponse::Ok
98 } else {
99 FsAllowlistResponse::Err("Failed to add one or more paths".to_string())
100 }
101 }
102 FsAllowlistCommand::RemovePaths { paths } => {
103 info!("Allowlist socket: Remove paths {:?}", paths);
104 let mut al_guard = allowlist.write().expect(
105 "Allowlist lock poisoned during write (remove_paths). Terminating.",
106 );
107 let mut al_clone = al_guard.clone();
108 let mut success = true;
109 for path in &paths {
110 if !al_clone.remove_path(path) {
111 error!("Allowlist socket: Failed to remove path: {:?}", path);
112 success = false;
113 break;
114 }
115 }
116 if success {
117 *al_guard = al_clone;
118 FsAllowlistResponse::Ok
119 } else {
120 FsAllowlistResponse::Err(
121 "Failed to remove one or more paths".to_string(),
122 )
123 }
124 }
125 };
126
127 if let Err(e) = tube.send(&result) {
128 error!("Allowlist socket: Failed to send response: {}", e);
129 }
130 }
131 Err(base::TubeError::Disconnected) => {
132 info!("Allowlist socket: Client disconnected");
133 break;
134 }
135 Err(e) => {
136 error!("Allowlist socket: Error reading from control socket: {}", e);
137 break;
138 }
139 }
140 }
141}
142
143fn run_allowlist_listener(fd: SafeDescriptor, allowlist: Arc<RwLock<PathAllowlist>>) {
144 let raw_fd = fd.into_raw_descriptor();
150 let path = format!("/proc/self/fd/{raw_fd}");
151 let listener = match UnixSeqpacketListener::bind(&path) {
152 Ok(l) => l,
153 Err(e) => {
154 error!(
155 "Allowlist socket: Failed to re-create listener from fd: {}",
156 e
157 );
158 let _ = unsafe { SafeDescriptor::from_raw_descriptor(raw_fd) };
161 return;
162 }
163 };
164
165 loop {
166 match listener.accept() {
167 Ok(seqpacket) => {
168 let tube = match Tube::try_from(seqpacket) {
169 Ok(t) => t,
170 Err(e) => {
171 error!("Allowlist socket: Failed to create Tube: {}", e);
172 continue;
173 }
174 };
175 handle_client_session(tube, &allowlist);
176 }
177 Err(e) => {
178 error!("Allowlist socket: Accept failed: {}", e);
179 break;
180 }
181 }
182 }
183}
184
185impl FsBackend {
186 #[allow(unused_variables)]
187 pub fn new(
188 tag: &str,
189 shared_dir: &str,
190 skip_pivot_root: bool,
191 cfg: Option<Config>,
192 allowlist_socket_fd: Option<RawDescriptor>,
193 ) -> anyhow::Result<Self> {
194 if tag.len() > FS_MAX_TAG_LEN {
195 bail!(
196 "fs tag is too long: {} (max supported: {})",
197 tag.len(),
198 FS_MAX_TAG_LEN
199 );
200 }
201
202 let avail_features = virtio::base_features(ProtectionType::Unprotected)
203 | 1 << VHOST_USER_F_PROTOCOL_FEATURES;
204
205 let cfg = cfg.unwrap_or_default();
206
207 #[cfg(any(target_os = "android", target_os = "linux"))]
208 let unmap_guest_memory_on_fork = cfg.unmap_guest_memory_on_fork;
209 #[cfg(not(any(target_os = "android", target_os = "linux")))]
210 let unmap_guest_memory_on_fork = false;
211
212 #[allow(unused_mut)]
214 let mut fs = PassthroughFs::new(tag, cfg)?;
215 #[cfg(feature = "fs_runtime_ugid_map")]
216 if skip_pivot_root {
217 fs.set_root_dir(shared_dir.to_string())?;
218 }
219
220 let allowlist_socket_fd = allowlist_socket_fd.map(|fd| {
221 unsafe { SafeDescriptor::from_raw_descriptor(fd) }
223 });
224
225 let allowlist = if allowlist_socket_fd.is_some() {
226 let al = Arc::new(RwLock::new(PathAllowlist::new()));
227 fs.set_allowlist(Some(al.clone()));
228 Some(al)
229 } else {
230 None
231 };
232
233 let mut keep_rds: Vec<RawDescriptor> = [0, 1, 2].to_vec();
234 keep_rds.append(&mut fs.keep_rds());
235 if let Some(ref fd) = allowlist_socket_fd {
236 keep_rds.push(fd.as_raw_descriptor());
237 }
238
239 let server = Arc::new(Server::new(fs));
240
241 Ok(FsBackend {
242 server,
243 tag: tag.to_owned(),
244 avail_features,
245 workers: Default::default(),
246 keep_rds,
247 unmap_guest_memory_on_fork,
248 allowlist_socket_fd,
249 allowlist,
250 })
251 }
252
253 pub fn start_allowlist_listener(&mut self) {
254 if let Some(fd) = self.allowlist_socket_fd.take() {
255 if let Some(allowlist) = &self.allowlist {
256 let allowlist = allowlist.clone();
257 let result = std::thread::Builder::new()
258 .name("fs_allowlist_listener".to_string())
259 .spawn(move || {
260 run_allowlist_listener(fd, allowlist);
261 });
262 if let Err(e) = result {
263 error!("Failed to spawn allowlist listener thread: {}", e);
264 }
265 }
266 }
267 }
268}
269
270impl VhostUserDevice for FsBackend {
271 fn max_queue_num(&self) -> usize {
272 MAX_QUEUE_NUM
273 }
274
275 fn features(&self) -> u64 {
276 self.avail_features
277 }
278
279 fn protocol_features(&self) -> VhostUserProtocolFeatures {
280 VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
281 }
282
283 fn read_config(&self, offset: u64, data: &mut [u8]) {
284 let mut config = virtio_fs_config {
285 tag: [0; FS_MAX_TAG_LEN],
286 num_request_queues: Le32::from(1),
287 };
288 config.tag[..self.tag.len()].copy_from_slice(self.tag.as_bytes());
289 copy_config(data, 0, config.as_bytes(), offset);
290 }
291
292 fn reset(&mut self) {
293 for worker in std::mem::take(&mut self.workers).into_values() {
294 let _ = worker.stop();
295 }
296 }
297
298 fn start_queue(
299 &mut self,
300 idx: usize,
301 queue: virtio::Queue,
302 _mem: GuestMemory,
303 ) -> anyhow::Result<()> {
304 if self.workers.contains_key(&idx) {
305 warn!("Starting new queue handler without stopping old handler");
306 self.stop_queue(idx)?;
307 }
308
309 let (_, fs_device_tube) = Tube::pair()?;
310 let tube = Arc::new(Mutex::new(fs_device_tube));
311
312 let server = self.server.clone();
313
314 let slot: u32 = 0;
316
317 let worker = WorkerThread::start(format!("v_fs:{}:{}", self.tag, idx), move |kill_evt| {
318 let mut worker = Worker::new(queue, server, tube, slot);
319 if let Err(e) = worker.run(kill_evt) {
320 error!("vhost-user-fs worker failed: {e:#}");
321 }
322 worker.queue
323 });
324 self.workers.insert(idx, worker);
325
326 Ok(())
327 }
328
329 fn stop_queue(&mut self, idx: usize) -> anyhow::Result<virtio::Queue> {
330 info!("Stopping vhost-user fs queue [{idx}]");
332 if let Some(worker) = self.workers.remove(&idx) {
333 let queue = worker.stop();
334 Ok(queue)
335 } else {
336 Err(anyhow::Error::new(DeviceError::WorkerNotFound))
337 }
338 }
339
340 fn unmap_guest_memory_on_fork(&self) -> bool {
341 self.unmap_guest_memory_on_fork
342 }
343
344 fn enter_suspended_state(&mut self) -> anyhow::Result<()> {
345 Ok(())
347 }
348
349 fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
350 bail!("snapshot not implemented for vhost-user fs");
351 }
352
353 fn restore(&mut self, _data: AnySnapshot) -> anyhow::Result<()> {
354 bail!("snapshot not implemented for vhost-user fs");
355 }
356}
357
358#[derive(FromArgs)]
359#[argh(subcommand, name = "fs")]
360pub struct Options {
362 #[argh(option, arg_name = "PATH", hidden_help)]
363 socket: Option<String>,
365 #[argh(option, arg_name = "PATH")]
366 socket_path: Option<String>,
369 #[argh(option, arg_name = "FD")]
370 fd: Option<RawDescriptor>,
373 #[argh(option, arg_name = "PATH")]
374 allowlist_socket_path: Option<PathBuf>,
378
379 #[argh(option, arg_name = "TAG")]
380 tag: String,
382 #[argh(option, arg_name = "DIR")]
383 shared_dir: PathBuf,
385 #[argh(option, arg_name = "UIDMAP")]
386 uid_map: Option<String>,
388 #[argh(option, arg_name = "GIDMAP")]
389 gid_map: Option<String>,
391 #[argh(option, arg_name = "CFG")]
392 cfg: Option<Config>,
397 #[argh(option, arg_name = "UID", default = "0")]
398 uid: u32,
411 #[argh(option, arg_name = "GID", default = "0")]
412 gid: u32,
415 #[argh(switch)]
416 disable_sandbox: bool,
422 #[argh(option, arg_name = "skip_pivot_root", default = "false")]
423 #[allow(dead_code)]
434 skip_pivot_root: bool,
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn test_run_allowlist_listener() {
443 let temp_dir = tempfile::TempDir::new().unwrap();
444 let socket_path = temp_dir.path().join("test.sock");
445 let listener = UnixSeqpacketListener::bind(&socket_path).unwrap();
446 let allowlist = Arc::new(RwLock::new(PathAllowlist::default()));
447
448 use std::os::fd::OwnedFd;
449 let fd = SafeDescriptor::from(OwnedFd::from(listener));
450 let fd_clone = fd.try_clone().unwrap();
451
452 let allowlist_clone = allowlist.clone();
453 let handle = std::thread::spawn(move || {
454 run_allowlist_listener(fd, allowlist_clone);
455 });
456
457 use base::UnixSeqpacket;
458 let client_socket = UnixSeqpacket::connect(&socket_path).unwrap();
459 let client_tube = Tube::try_from(client_socket).unwrap();
460
461 client_tube
463 .send(&FsAllowlistCommand::AddPaths {
464 paths: vec!["/allowed_path1".into(), "/allowed_path2".into()],
465 })
466 .unwrap();
467 let resp: FsAllowlistResponse = client_tube.recv().unwrap();
468 assert!(matches!(resp, FsAllowlistResponse::Ok));
469
470 {
472 let al = allowlist.read().unwrap();
473 assert!(al.is_accessible("/allowed_path1"));
474 assert!(al.is_accessible("/allowed_path2"));
475 }
476
477 client_tube
479 .send(&FsAllowlistCommand::RemovePaths {
480 paths: vec!["/allowed_path1".into(), "/allowed_path2".into()],
481 })
482 .unwrap();
483 let resp: FsAllowlistResponse = client_tube.recv().unwrap();
484 assert!(matches!(resp, FsAllowlistResponse::Ok));
485
486 {
488 let al = allowlist.read().unwrap();
489 assert!(!al.is_accessible("/allowed_path1"));
490 assert!(!al.is_accessible("/allowed_path2"));
491 }
492
493 client_tube
495 .send(&FsAllowlistCommand::AddPaths {
496 paths: vec!["/valid_but_rolled_back".into(), "/a/../../..".into()],
497 })
498 .unwrap();
499 let resp: FsAllowlistResponse = client_tube.recv().unwrap();
500 assert!(matches!(resp, FsAllowlistResponse::Err(_)));
501
502 {
504 let al = allowlist.read().unwrap();
505 assert!(!al.is_accessible("/valid_but_rolled_back"));
506 }
507
508 drop(client_tube);
510
511 unsafe {
514 libc::shutdown(fd_clone.as_raw_descriptor(), libc::SHUT_RDWR);
515 }
516
517 let join_res = handle.join();
518 assert!(join_res.is_ok(), "Listener thread panicked!");
519 }
520}