1use std::borrow::Cow;
6use std::cell::RefCell;
7use std::cmp;
8use std::collections::btree_map;
9use std::collections::BTreeMap;
10use std::ffi::CStr;
11use std::ffi::CString;
12#[cfg(feature = "fs_runtime_ugid_map")]
13use std::ffi::OsStr;
14use std::fs::File;
15use std::io;
16use std::mem;
17use std::mem::size_of;
18use std::mem::MaybeUninit;
19use std::os::raw::c_int;
20use std::os::raw::c_long;
21#[cfg(feature = "fs_runtime_ugid_map")]
22use std::os::unix::ffi::OsStrExt;
23#[cfg(feature = "fs_runtime_ugid_map")]
24use std::path::Path;
25use std::ptr;
26use std::ptr::addr_of;
27use std::ptr::addr_of_mut;
28use std::sync::atomic::AtomicBool;
29use std::sync::atomic::AtomicU64;
30use std::sync::atomic::Ordering;
31use std::sync::Arc;
32use std::sync::MutexGuard;
33use std::sync::RwLock;
34use std::time::Duration;
35
36#[cfg(feature = "arc_quota")]
37use base::debug;
38use base::error;
39use base::ioctl_ior_nr;
40use base::ioctl_iow_nr;
41use base::ioctl_iowr_nr;
42use base::ioctl_with_mut_ptr;
43use base::ioctl_with_ptr;
44use base::open_how;
45use base::openat2;
46use base::syscall;
47use base::unix::FileFlags;
48use base::warn;
49use base::AsRawDescriptor;
50use base::FromRawDescriptor;
51use base::IntoRawDescriptor;
52use base::IoctlNr;
53use base::Protection;
54use base::RawDescriptor;
55use fuse::filesystem::Context;
56use fuse::filesystem::DirectoryIterator;
57use fuse::filesystem::Entry;
58use fuse::filesystem::FileSystem;
59use fuse::filesystem::FsOptions;
60use fuse::filesystem::GetxattrReply;
61use fuse::filesystem::IoctlFlags;
62use fuse::filesystem::IoctlReply;
63use fuse::filesystem::ListxattrReply;
64use fuse::filesystem::OpenOptions;
65use fuse::filesystem::RemoveMappingOne;
66use fuse::filesystem::SetattrValid;
67use fuse::filesystem::ZeroCopyReader;
68use fuse::filesystem::ZeroCopyWriter;
69use fuse::filesystem::ROOT_ID;
70use fuse::sys::WRITE_KILL_PRIV;
71use fuse::Mapper;
72#[cfg(feature = "arc_quota")]
73use protobuf::Message;
74use sync::Mutex;
75#[cfg(feature = "arc_quota")]
76use system_api::client::OrgChromiumSpaced;
77#[cfg(feature = "arc_quota")]
78use system_api::spaced::SetProjectIdReply;
79#[cfg(feature = "arc_quota")]
80use system_api::spaced::SetProjectInheritanceFlagReply;
81use zerocopy::FromBytes;
82use zerocopy::FromZeros;
83use zerocopy::Immutable;
84use zerocopy::IntoBytes;
85use zerocopy::KnownLayout;
86
87use crate::virtio::fs::allowlist::PathAllowlist;
88#[cfg(feature = "arc_quota")]
89use crate::virtio::fs::arc_ioctl::FsPathXattrDataBuffer;
90#[cfg(feature = "arc_quota")]
91use crate::virtio::fs::arc_ioctl::FsPermissionDataBuffer;
92#[cfg(feature = "arc_quota")]
93use crate::virtio::fs::arc_ioctl::XattrData;
94use crate::virtio::fs::caps::Capability;
95use crate::virtio::fs::caps::Caps;
96use crate::virtio::fs::caps::Set as CapSet;
97use crate::virtio::fs::caps::Value as CapValue;
98use crate::virtio::fs::config::CachePolicy;
99use crate::virtio::fs::config::Config;
100#[cfg(feature = "fs_permission_translation")]
101use crate::virtio::fs::config::PermissionData;
102use crate::virtio::fs::expiring_map::ExpiringMap;
103use crate::virtio::fs::multikey::MultikeyBTreeMap;
104use crate::virtio::fs::read_dir::ReadDir;
105
106const RESOLVE_NO_MAGICLINKS: u64 = 0x02;
109const RESOLVE_NO_SYMLINKS: u64 = 0x04;
110const RESOLVE_IN_ROOT: u64 = 0x10;
111
112const EMPTY_CSTR: &CStr = c"";
113const PROC_CSTR: &CStr = c"/proc";
114const UNLABELED_CSTR: &CStr = c"unlabeled";
115
116const USER_VIRTIOFS_XATTR: &[u8] = b"user.virtiofs.";
117const SECURITY_XATTR: &[u8] = b"security.";
118const SELINUX_XATTR: &[u8] = b"security.selinux";
119
120const FSCRYPT_KEY_DESCRIPTOR_SIZE: usize = 8;
121const FSCRYPT_KEY_IDENTIFIER_SIZE: usize = 16;
122
123#[cfg(feature = "arc_quota")]
124const FS_PROJINHERIT_FL: c_int = 0x20000000;
125
126#[cfg(feature = "arc_quota")]
128const DEFAULT_DBUS_TIMEOUT: Duration = Duration::from_secs(25);
129
130macro_rules! fs_trace {
132 ($tag:expr, $name:expr, $($arg:expr),+) => {
133 cros_tracing::trace_event!(VirtioFs, $name, $tag, $($arg),*)
134 };
135}
136
137#[repr(C)]
138#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
139struct fscrypt_policy_v1 {
140 _version: u8,
141 _contents_encryption_mode: u8,
142 _filenames_encryption_mode: u8,
143 _flags: u8,
144 _master_key_descriptor: [u8; FSCRYPT_KEY_DESCRIPTOR_SIZE],
145}
146
147#[repr(C)]
148#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
149struct fscrypt_policy_v2 {
150 _version: u8,
151 _contents_encryption_mode: u8,
152 _filenames_encryption_mode: u8,
153 _flags: u8,
154 __reserved: [u8; 4],
155 master_key_identifier: [u8; FSCRYPT_KEY_IDENTIFIER_SIZE],
156}
157
158#[repr(C)]
159#[derive(Copy, Clone, FromBytes, Immutable, KnownLayout)]
160union fscrypt_policy {
161 _version: u8,
162 _v1: fscrypt_policy_v1,
163 _v2: fscrypt_policy_v2,
164}
165
166#[repr(C)]
167#[derive(Copy, Clone, FromBytes, Immutable, KnownLayout)]
168struct fscrypt_get_policy_ex_arg {
169 policy_size: u64, policy: fscrypt_policy, }
172
173impl From<&fscrypt_get_policy_ex_arg> for &[u8] {
174 fn from(value: &fscrypt_get_policy_ex_arg) -> Self {
175 assert!(value.policy_size <= size_of::<fscrypt_policy>() as u64);
176 let data_raw: *const fscrypt_get_policy_ex_arg = value;
177 unsafe {
179 std::slice::from_raw_parts(
180 data_raw.cast(),
181 value.policy_size as usize + size_of::<u64>(),
182 )
183 }
184 }
185}
186
187ioctl_iowr_nr!(FS_IOC_GET_ENCRYPTION_POLICY_EX, 'f' as u32, 22, [u8; 9]);
188
189#[repr(C)]
190#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
191struct fsxattr {
192 fsx_xflags: u32, fsx_extsize: u32, fsx_nextents: u32, fsx_projid: u32, fsx_cowextsize: u32, fsx_pad: [u8; 8],
198}
199
200ioctl_ior_nr!(FS_IOC_FSGETXATTR, 'X' as u32, 31, fsxattr);
201ioctl_iow_nr!(FS_IOC_FSSETXATTR, 'X' as u32, 32, fsxattr);
202
203ioctl_ior_nr!(FS_IOC_GETFLAGS, 'f' as u32, 1, c_long);
204ioctl_iow_nr!(FS_IOC_SETFLAGS, 'f' as u32, 2, c_long);
205
206ioctl_ior_nr!(FS_IOC32_GETFLAGS, 'f' as u32, 1, u32);
207ioctl_iow_nr!(FS_IOC32_SETFLAGS, 'f' as u32, 2, u32);
208
209ioctl_ior_nr!(FS_IOC64_GETFLAGS, 'f' as u32, 1, u64);
210ioctl_iow_nr!(FS_IOC64_SETFLAGS, 'f' as u32, 2, u64);
211
212#[cfg(feature = "arc_quota")]
213ioctl_iow_nr!(FS_IOC_SETPERMISSION, 'f' as u32, 1, FsPermissionDataBuffer);
214#[cfg(feature = "arc_quota")]
215ioctl_iow_nr!(FS_IOC_SETPATHXATTR, 'f' as u32, 1, FsPathXattrDataBuffer);
216
217#[repr(C)]
218#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
219struct fsverity_enable_arg {
220 _version: u32,
221 _hash_algorithm: u32,
222 _block_size: u32,
223 salt_size: u32,
224 salt_ptr: u64,
225 sig_size: u32,
226 __reserved1: u32,
227 sig_ptr: u64,
228 __reserved2: [u64; 11],
229}
230
231#[repr(C)]
232#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
233struct fsverity_digest {
234 _digest_algorithm: u16,
235 digest_size: u16,
236 }
238
239ioctl_iow_nr!(FS_IOC_ENABLE_VERITY, 'f' as u32, 133, fsverity_enable_arg);
240ioctl_iowr_nr!(FS_IOC_MEASURE_VERITY, 'f' as u32, 134, fsverity_digest);
241
242pub type Inode = u64;
243type Handle = u64;
244
245#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
246struct InodeAltKey {
247 ino: libc::ino64_t,
248 dev: libc::dev_t,
249}
250
251#[derive(PartialEq, Eq, Debug)]
252enum FileType {
253 Regular,
254 Directory,
255 Other,
256}
257
258impl From<libc::mode_t> for FileType {
259 fn from(mode: libc::mode_t) -> Self {
260 match mode & libc::S_IFMT {
261 libc::S_IFREG => FileType::Regular,
262 libc::S_IFDIR => FileType::Directory,
263 _ => FileType::Other,
264 }
265 }
266}
267
268#[derive(Debug)]
269struct OpenedFile {
270 file: Option<File>,
271 open_flags: libc::c_int,
272}
273
274impl AsRawDescriptor for OpenedFile {
275 fn as_raw_descriptor(&self) -> RawDescriptor {
276 self.file().as_raw_descriptor()
277 }
278}
279
280impl OpenedFile {
281 fn new(file: File, open_flags: libc::c_int) -> Self {
282 OpenedFile {
283 file: Some(file),
284 open_flags,
285 }
286 }
287
288 fn file(&self) -> &File {
289 self.file.as_ref().expect("must have a file")
290 }
291
292 fn file_mut(&mut self) -> &mut File {
293 self.file.as_mut().expect("must have a file")
294 }
295
296 fn leak_fd(&mut self) {
303 let f = self.file.take().expect("must have a file");
304 let _ = f.into_raw_descriptor();
305 }
306}
307
308#[derive(Debug)]
309struct InodeData {
310 inode: Inode,
311 file: Mutex<OpenedFile>,
313 refcount: AtomicU64,
314 filetype: FileType,
315 path: String,
316 unsafe_leak_fd: AtomicBool,
318}
319
320impl AsRawDescriptor for InodeData {
321 fn as_raw_descriptor(&self) -> RawDescriptor {
322 self.file.lock().as_raw_descriptor()
323 }
324}
325
326impl Drop for InodeData {
327 fn drop(&mut self) {
333 if self.unsafe_leak_fd.load(Ordering::Relaxed) {
334 self.file.get_mut().leak_fd();
335 }
336 }
337}
338
339impl InodeData {
340 fn set_unsafe_leak_fd(&self) {
341 self.unsafe_leak_fd.store(true, Ordering::Relaxed);
342 }
343}
344
345#[derive(Debug)]
346struct HandleData {
347 inode: Inode,
348 file: Mutex<OpenedFile>,
349
350 unsafe_leak_fd: AtomicBool,
351}
352
353impl AsRawDescriptor for HandleData {
354 fn as_raw_descriptor(&self) -> RawDescriptor {
355 self.file.lock().as_raw_descriptor()
356 }
357}
358
359impl Drop for HandleData {
360 fn drop(&mut self) {
366 if self.unsafe_leak_fd.load(Ordering::Relaxed) {
367 self.file.get_mut().leak_fd();
368 }
369 }
370}
371
372impl HandleData {
373 fn set_unsafe_leak_fd(&self) {
374 self.unsafe_leak_fd.store(true, Ordering::Relaxed);
375 }
376}
377
378macro_rules! scoped_cred {
379 ($name:ident, $ty:ty, $syscall_nr:expr) => {
380 #[derive(Debug)]
381 struct $name {
382 old: $ty,
383 }
384
385 impl $name {
386 fn new(val: $ty, old: $ty) -> io::Result<Option<$name>> {
389 if val == old {
390 return Ok(None);
392 }
393
394 let res = unsafe { libc::syscall($syscall_nr, -1, val, -1) };
409 if res == 0 {
410 Ok(Some($name { old }))
411 } else {
412 Err(io::Error::last_os_error())
413 }
414 }
415 }
416
417 impl Drop for $name {
418 fn drop(&mut self) {
419 let res = unsafe { libc::syscall($syscall_nr, -1, self.old, -1) };
421 if res < 0 {
422 error!(
423 "failed to change credentials back to {}: {}",
424 self.old,
425 io::Error::last_os_error(),
426 );
427 }
428 }
429 }
430 };
431}
432scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid);
433scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid);
434
435const SYS_GETEUID: libc::c_long = libc::SYS_geteuid;
436const SYS_GETEGID: libc::c_long = libc::SYS_getegid;
437
438thread_local! {
439 static THREAD_EUID: libc::uid_t = unsafe { libc::syscall(SYS_GETEUID) as libc::uid_t };
442 static THREAD_EGID: libc::gid_t = unsafe { libc::syscall(SYS_GETEGID) as libc::gid_t };
445}
446
447fn set_creds(
448 uid: libc::uid_t,
449 gid: libc::gid_t,
450) -> io::Result<(Option<ScopedUid>, Option<ScopedGid>)> {
451 let olduid = THREAD_EUID.with(|uid| *uid);
452 let oldgid = THREAD_EGID.with(|gid| *gid);
453
454 ScopedGid::new(gid, oldgid).and_then(|gid| Ok((ScopedUid::new(uid, olduid)?, gid)))
457}
458
459thread_local!(static THREAD_FSCREATE: RefCell<Option<File>> = const { RefCell::new(None) });
460
461fn open_fscreate(proc: &File) -> File {
464 let fscreate = c"thread-self/attr/fscreate";
465
466 let raw_descriptor = unsafe {
468 libc::openat(
469 proc.as_raw_descriptor(),
470 fscreate.as_ptr(),
471 libc::O_CLOEXEC | libc::O_WRONLY,
472 )
473 };
474
475 if raw_descriptor < 0 {
478 panic!(
479 "Failed to open /proc/thread-self/attr/fscreate: {}",
480 io::Error::last_os_error()
481 );
482 }
483
484 unsafe { File::from_raw_descriptor(raw_descriptor) }
486}
487
488struct ScopedSecurityContext;
489
490impl ScopedSecurityContext {
491 fn new(proc: &File, ctx: &CStr) -> io::Result<ScopedSecurityContext> {
492 THREAD_FSCREATE.with(|thread_fscreate| {
493 let mut fscreate = thread_fscreate.borrow_mut();
494 let file = fscreate.get_or_insert_with(|| open_fscreate(proc));
495 let ret = unsafe {
497 libc::write(
498 file.as_raw_descriptor(),
499 ctx.as_ptr() as *const libc::c_void,
500 ctx.to_bytes_with_nul().len(),
501 )
502 };
503 if ret < 0 {
504 Err(io::Error::last_os_error())
505 } else {
506 Ok(ScopedSecurityContext)
507 }
508 })
509 }
510}
511
512impl Drop for ScopedSecurityContext {
513 fn drop(&mut self) {
514 THREAD_FSCREATE.with(|thread_fscreate| {
515 let fscreate = thread_fscreate.borrow();
518 let file = fscreate
519 .as_ref()
520 .expect("Uninitialized thread-local when dropping ScopedSecurityContext");
521
522 let ret = unsafe { libc::write(file.as_raw_descriptor(), ptr::null(), 0) };
524
525 if ret < 0 {
526 warn!(
527 "Failed to restore security context: {}",
528 io::Error::last_os_error()
529 );
530 }
531 })
532 }
533}
534
535struct ScopedUmask {
536 old: libc::mode_t,
537 mask: libc::mode_t,
538}
539
540impl ScopedUmask {
541 fn new(mask: libc::mode_t) -> ScopedUmask {
542 ScopedUmask {
543 old: unsafe { libc::umask(mask) },
545 mask,
546 }
547 }
548}
549
550impl Drop for ScopedUmask {
551 fn drop(&mut self) {
552 let previous = unsafe { libc::umask(self.old) };
554 debug_assert_eq!(
555 previous, self.mask,
556 "umask changed while holding ScopedUmask"
557 );
558 }
559}
560
561struct ScopedFsetid(Caps);
562impl Drop for ScopedFsetid {
563 fn drop(&mut self) {
564 if let Err(e) = raise_cap_fsetid(&mut self.0) {
565 error!(
566 "Failed to restore CAP_FSETID: {}. Some operations may be broken.",
567 e
568 )
569 }
570 }
571}
572
573fn raise_cap_fsetid(c: &mut Caps) -> io::Result<()> {
574 c.update(&[Capability::Fsetid], CapSet::Effective, CapValue::Set)?;
575 c.apply()
576}
577
578fn drop_cap_fsetid() -> io::Result<ScopedFsetid> {
581 let mut caps = Caps::for_current_thread()?;
582 caps.update(&[Capability::Fsetid], CapSet::Effective, CapValue::Clear)?;
583 caps.apply()?;
584 Ok(ScopedFsetid(caps))
585}
586
587fn ebadf() -> io::Error {
588 io::Error::from_raw_os_error(libc::EBADF)
589}
590
591fn eexist() -> io::Error {
592 io::Error::from_raw_os_error(libc::EEXIST)
593}
594
595fn stat<F: AsRawDescriptor + ?Sized>(f: &F) -> io::Result<libc::stat64> {
596 let mut st: MaybeUninit<libc::stat64> = MaybeUninit::<libc::stat64>::zeroed();
597
598 syscall!(unsafe {
600 libc::fstatat64(
601 f.as_raw_descriptor(),
602 EMPTY_CSTR.as_ptr(),
603 st.as_mut_ptr(),
604 libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW,
605 )
606 })?;
607
608 Ok(unsafe { st.assume_init() })
610}
611
612fn validate_path_component(name: &CStr) -> io::Result<()> {
613 let bytes = name.to_bytes();
614 if bytes == b".." || (bytes.contains(&b'/') && bytes != b"/") {
615 return Err(io::Error::from_raw_os_error(libc::EINVAL));
616 }
617 Ok(())
618}
619
620fn safe_openat2<D: AsRawDescriptor>(
627 dir: &D,
628 name: &CStr,
629 flags: libc::c_int,
630 mode: Option<libc::mode_t>,
631 resolve: u64,
632) -> io::Result<File> {
633 let mut how = open_how {
634 flags: flags as u64,
635 resolve,
636 ..Default::default()
637 };
638 if let Some(m) = mode {
639 how.mode = (m & 0o7777) as u64;
640 }
641
642 let res = openat2(dir, name, &how);
643 match res {
644 Ok(file) => Ok(file),
645 Err(e) if e.errno() == libc::ENOSYS => {
646 let fd = if let Some(m) = mode {
648 syscall!(unsafe {
650 libc::openat64(dir.as_raw_descriptor(), name.as_ptr(), flags, m)
651 })
652 } else {
653 syscall!(unsafe { libc::openat64(dir.as_raw_descriptor(), name.as_ptr(), flags) })
655 }?;
656 Ok(unsafe { File::from_raw_descriptor(fd) })
658 }
659 Err(e) => Err(e.into()),
660 }
661}
662
663#[cfg(feature = "arc_quota")]
664fn is_android_project_id(project_id: u32) -> bool {
665 const PROJECT_ID_FOR_ANDROID_FILES: std::ops::RangeInclusive<u32> = 1000..=1099;
672 const PROJECT_ID_FOR_ANDROID_APPS: std::ops::RangeInclusive<u32> = 20000..=69999;
677
678 PROJECT_ID_FOR_ANDROID_FILES.contains(&project_id)
679 || PROJECT_ID_FOR_ANDROID_APPS.contains(&project_id)
680}
681
682struct CasefoldCache(BTreeMap<Vec<u8>, CString>);
691
692impl CasefoldCache {
693 fn new(dir: &InodeData) -> io::Result<Self> {
694 let mut mp = BTreeMap::new();
695
696 let mut buf = [0u8; 1024];
697 let mut offset = 0;
698 loop {
699 let mut read_dir = ReadDir::new(dir, offset, &mut buf[..])?;
700 if read_dir.remaining() == 0 {
701 break;
702 }
703
704 while let Some(entry) = read_dir.next() {
705 offset = entry.offset as libc::off64_t;
706 let entry_name = entry.name;
707 mp.insert(
708 entry_name.to_bytes().to_ascii_lowercase(),
709 entry_name.to_owned(),
710 );
711 }
712 }
713 Ok(Self(mp))
714 }
715
716 fn insert(&mut self, name: &CStr) {
717 let lower_case = name.to_bytes().to_ascii_lowercase();
718 self.0.insert(lower_case, name.into());
719 }
720
721 fn lookup(&self, name: &[u8]) -> Option<CString> {
722 let lower = name.to_ascii_lowercase();
723 self.0.get(&lower).cloned()
724 }
725
726 fn remove(&mut self, name: &CStr) {
727 let lower_case = name.to_bytes().to_ascii_lowercase();
728 self.0.remove(&lower_case);
729 }
730}
731
732struct ExpiringCasefoldLookupCaches {
736 inner: ExpiringMap<Inode, CasefoldCache>,
737}
738
739impl ExpiringCasefoldLookupCaches {
740 fn new(timeout: Duration) -> Self {
741 Self {
742 inner: ExpiringMap::new(timeout),
743 }
744 }
745
746 fn insert(&mut self, parent: Inode, name: &CStr) {
747 if let Some(dir_cache) = self.inner.get_mut(&parent) {
748 dir_cache.insert(name);
749 }
750 }
751
752 fn remove(&mut self, parent: Inode, name: &CStr) {
753 if let Some(dir_cache) = self.inner.get_mut(&parent) {
754 dir_cache.remove(name);
755 }
756 }
757
758 fn forget(&mut self, parent: Inode) {
759 self.inner.remove(&parent);
760 }
761
762 fn get(&mut self, parent: &InodeData) -> io::Result<&CasefoldCache> {
766 self.inner
767 .get_or_insert_with(&parent.inode, || CasefoldCache::new(parent))
768 }
769
770 #[cfg(test)]
771 fn exists_in_cache(&mut self, parent: Inode, name: &CStr) -> bool {
772 if let Some(dir_cache) = self.inner.get(&parent) {
773 dir_cache.lookup(name.to_bytes()).is_some()
774 } else {
775 false
776 }
777 }
778}
779
780#[cfg(feature = "fs_permission_translation")]
781impl PermissionData {
782 pub(crate) fn need_set_permission(&self, path: &str) -> bool {
783 path.starts_with(&self.perm_path)
784 }
785}
786
787pub struct PassthroughFs {
802 process_lock: Mutex<()>,
804 tag: String,
807
808 inodes: Mutex<MultikeyBTreeMap<Inode, InodeAltKey, Arc<InodeData>>>,
810 next_inode: AtomicU64,
811
812 handles: Mutex<BTreeMap<Handle, Arc<HandleData>>>,
815 next_handle: AtomicU64,
816
817 proc: File,
822
823 writeback: AtomicBool,
826
827 zero_message_open: AtomicBool,
829
830 zero_message_opendir: AtomicBool,
832
833 #[cfg(feature = "arc_quota")]
835 dbus_connection: Option<Mutex<dbus::blocking::Connection>>,
836 #[cfg(feature = "arc_quota")]
837 dbus_fd: Option<std::os::unix::io::RawFd>,
838
839 expiring_casefold_lookup_caches: Option<Mutex<ExpiringCasefoldLookupCaches>>,
846
847 #[cfg(feature = "fs_permission_translation")]
849 permission_paths: RwLock<Vec<PermissionData>>,
850
851 #[cfg(feature = "arc_quota")]
853 xattr_paths: RwLock<Vec<XattrData>>,
854
855 cfg: Config,
856
857 root_dir: String,
866 allowlist: Option<Arc<RwLock<PathAllowlist>>>,
867}
868
869impl std::fmt::Debug for PassthroughFs {
870 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
871 f.debug_struct("PassthroughFs")
872 .field("tag", &self.tag)
873 .field("next_inode", &self.next_inode)
874 .field("next_handle", &self.next_handle)
875 .field("proc", &self.proc)
876 .field("writeback", &self.writeback)
877 .field("zero_message_open", &self.zero_message_open)
878 .field("zero_message_opendir", &self.zero_message_opendir)
879 .field("cfg", &self.cfg)
880 .finish()
881 }
882}
883
884impl PassthroughFs {
885 pub fn new(tag: &str, cfg: Config) -> io::Result<PassthroughFs> {
886 let raw_descriptor = syscall!(unsafe {
888 libc::openat64(
889 libc::AT_FDCWD,
890 PROC_CSTR.as_ptr(),
891 libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
892 )
893 })?;
894
895 #[cfg(feature = "arc_quota")]
897 let (dbus_connection, dbus_fd) = if cfg.privileged_quota_uids.is_empty() {
898 (None, None)
899 } else {
900 let mut channel = dbus::channel::Channel::get_private(dbus::channel::BusType::System)
901 .map_err(io::Error::other)?;
902 channel.set_watch_enabled(true);
903 let dbus_fd = channel.watch().fd;
904 channel.set_watch_enabled(false);
905 (
906 Some(Mutex::new(dbus::blocking::Connection::from(channel))),
907 Some(dbus_fd),
908 )
909 };
910
911 let proc = unsafe { File::from_raw_descriptor(raw_descriptor) };
913
914 let expiring_casefold_lookup_caches = if cfg.ascii_casefold {
915 Some(Mutex::new(ExpiringCasefoldLookupCaches::new(cfg.timeout)))
916 } else {
917 None
918 };
919
920 #[allow(unused_mut)]
921 let mut passthroughfs = PassthroughFs {
922 process_lock: Mutex::new(()),
923 tag: tag.to_string(),
924 inodes: Mutex::new(MultikeyBTreeMap::new()),
925 next_inode: AtomicU64::new(ROOT_ID + 1),
926
927 handles: Mutex::new(BTreeMap::new()),
928 next_handle: AtomicU64::new(1),
929
930 proc,
931
932 writeback: AtomicBool::new(false),
933 zero_message_open: AtomicBool::new(false),
934 zero_message_opendir: AtomicBool::new(false),
935
936 #[cfg(feature = "arc_quota")]
937 dbus_connection,
938 #[cfg(feature = "arc_quota")]
939 dbus_fd,
940 expiring_casefold_lookup_caches,
941 #[cfg(feature = "fs_permission_translation")]
942 permission_paths: RwLock::new(Vec::new()),
943 #[cfg(feature = "arc_quota")]
944 xattr_paths: RwLock::new(Vec::new()),
945 cfg,
946 root_dir: "/".to_string(),
947 allowlist: None,
948 };
949
950 #[cfg(feature = "fs_runtime_ugid_map")]
951 passthroughfs.set_permission_path();
952
953 cros_tracing::trace_simple_print!(
954 VirtioFs,
955 "New PassthroughFS initialized: {:?}",
956 passthroughfs
957 );
958 Ok(passthroughfs)
959 }
960
961 pub fn set_allowlist(&mut self, allowlist: Option<Arc<RwLock<PathAllowlist>>>) {
962 self.allowlist = allowlist;
963 }
964
965 fn is_path_accessible(&self, path: &str) -> bool {
966 self.allowlist
967 .as_ref()
968 .map(|al| al.read().unwrap().is_accessible(path))
969 .unwrap_or(true)
970 }
971
972 fn authorize_write_path(&self, parent_path: &str, name: &CStr) -> io::Result<String> {
987 validate_path_component(name)?;
988 let name_str = name
989 .to_str()
990 .map_err(|_| io::Error::from_raw_os_error(libc::EILSEQ))?;
991 let path = if parent_path.is_empty() || parent_path == "/" {
992 format!("/{name_str}")
993 } else {
994 format!("{parent_path}/{name_str}")
995 };
996 let is_writable = self
997 .allowlist
998 .as_ref()
999 .map(|al| al.read().unwrap().is_writable(&path))
1000 .unwrap_or(true);
1001 if !is_writable {
1002 return Err(io::Error::from_raw_os_error(libc::EACCES));
1003 }
1004 Ok(path)
1005 }
1006
1007 #[cfg(feature = "fs_runtime_ugid_map")]
1008 fn set_permission_path(&mut self) {
1009 if !self.cfg.ugid_map.is_empty() {
1010 let mut write_lock = self
1011 .permission_paths
1012 .write()
1013 .expect("Failed to acquire write lock on permission_paths");
1014 *write_lock = self.cfg.ugid_map.clone();
1015 }
1016 }
1017
1018 pub fn set_root_dir(&mut self, shared_dir: String) -> io::Result<()> {
1019 let canonicalized_root = match std::fs::canonicalize(shared_dir) {
1020 Ok(path) => path,
1021 Err(e) => {
1022 return Err(io::Error::new(
1023 io::ErrorKind::InvalidInput,
1024 format!("Failed to canonicalize root_dir: {e}"),
1025 ));
1026 }
1027 };
1028 self.root_dir = canonicalized_root.to_string_lossy().to_string();
1029 Ok(())
1030 }
1031
1032 pub fn cfg(&self) -> &Config {
1033 &self.cfg
1034 }
1035
1036 pub fn keep_rds(&self) -> Vec<RawDescriptor> {
1037 #[cfg_attr(not(feature = "arc_quota"), allow(unused_mut))]
1038 let mut keep_rds = vec![self.proc.as_raw_descriptor()];
1039 #[cfg(feature = "arc_quota")]
1040 if let Some(fd) = self.dbus_fd {
1041 keep_rds.push(fd);
1042 }
1043 keep_rds
1044 }
1045
1046 fn rewrite_xattr_name<'xattr>(&self, name: &'xattr CStr) -> Cow<'xattr, CStr> {
1047 if !self.cfg.rewrite_security_xattrs {
1048 return Cow::Borrowed(name);
1049 }
1050
1051 let buf = name.to_bytes();
1053 if !buf.starts_with(SECURITY_XATTR) || buf == SELINUX_XATTR {
1054 return Cow::Borrowed(name);
1055 }
1056
1057 let mut newname = USER_VIRTIOFS_XATTR.to_vec();
1058 newname.extend_from_slice(buf);
1059
1060 Cow::Owned(CString::new(newname).expect("Failed to re-write xattr name"))
1063 }
1064
1065 fn find_inode(&self, inode: Inode) -> io::Result<Arc<InodeData>> {
1066 self.inodes.lock().get(&inode).cloned().ok_or_else(ebadf)
1067 }
1068
1069 fn find_handle(&self, handle: Handle, inode: Inode) -> io::Result<Arc<HandleData>> {
1070 self.handles
1071 .lock()
1072 .get(&handle)
1073 .filter(|hd| hd.inode == inode)
1074 .cloned()
1075 .ok_or_else(ebadf)
1076 }
1077
1078 fn open_fd(&self, fd: RawDescriptor, flags: i32) -> io::Result<File> {
1079 let pathname = CString::new(format!("self/fd/{fd}"))
1080 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1081
1082 let raw_descriptor = syscall!(unsafe {
1087 libc::openat64(
1088 self.proc.as_raw_descriptor(),
1089 pathname.as_ptr(),
1090 (flags | libc::O_CLOEXEC) & !(libc::O_NOFOLLOW | libc::O_DIRECT),
1091 )
1092 })?;
1093
1094 Ok(unsafe { File::from_raw_descriptor(raw_descriptor) })
1096 }
1097
1098 fn update_open_flags(&self, mut flags: i32) -> i32 {
1101 let writeback = self.writeback.load(Ordering::Relaxed);
1105 if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
1106 flags &= !libc::O_ACCMODE;
1107 flags |= libc::O_RDWR;
1108 }
1109
1110 if writeback && flags & libc::O_APPEND != 0 {
1117 flags &= !libc::O_APPEND;
1118 }
1119
1120 flags
1121 }
1122
1123 fn open_inode(&self, inode: &InodeData, mut flags: i32) -> io::Result<File> {
1124 flags = self.update_open_flags(flags);
1126
1127 self.open_fd(inode.as_raw_descriptor(), flags)
1128 }
1129
1130 fn increase_inode_refcount(&self, inode_data: &InodeData) -> Inode {
1132 inode_data.refcount.fetch_add(1, Ordering::Acquire);
1134 inode_data.inode
1135 }
1136
1137 fn add_entry(
1141 &self,
1142 f: File,
1143 #[cfg_attr(not(feature = "fs_permission_translation"), allow(unused_mut))]
1144 mut st: libc::stat64,
1145 open_flags: libc::c_int,
1146 path: String,
1147 ) -> Entry {
1148 #[cfg(feature = "arc_quota")]
1149 self.set_permission(&mut st, &path);
1150 #[cfg(feature = "fs_runtime_ugid_map")]
1151 self.set_ugid_permission(&mut st, &path);
1152 let mut inodes = self.inodes.lock();
1153
1154 let altkey = InodeAltKey {
1155 ino: st.st_ino,
1156 dev: st.st_dev,
1157 };
1158
1159 let inode = if let Some(data) = inodes.get_alt(&altkey) {
1160 self.increase_inode_refcount(data)
1161 } else {
1162 let inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
1163 inodes.insert(
1164 inode,
1165 altkey,
1166 Arc::new(InodeData {
1167 inode,
1168 file: Mutex::new(OpenedFile::new(f, open_flags)),
1169 refcount: AtomicU64::new(1),
1170 filetype: st.st_mode.into(),
1171 path,
1172 unsafe_leak_fd: AtomicBool::new(false),
1173 }),
1174 );
1175
1176 inode
1177 };
1178
1179 Entry {
1180 inode,
1181 generation: 0,
1182 attr: st,
1183 attr_timeout: self.cfg.timeout,
1185 entry_timeout: self.cfg.timeout,
1186 }
1187 }
1188
1189 fn lock_casefold_lookup_caches(&self) -> Option<MutexGuard<'_, ExpiringCasefoldLookupCaches>> {
1191 self.expiring_casefold_lookup_caches
1192 .as_ref()
1193 .map(|c| c.lock())
1194 }
1195
1196 fn get_case_unfolded_name(
1200 &self,
1201 parent: &InodeData,
1202 name: &[u8],
1203 ) -> io::Result<Option<CString>> {
1204 let mut caches = self
1205 .lock_casefold_lookup_caches()
1206 .expect("casefold must be enabled");
1207 let dir_cache = caches.get(parent)?;
1208 Ok(dir_cache.lookup(name))
1209 }
1210
1211 fn ascii_casefold_lookup(&self, parent: &InodeData, name: &[u8]) -> io::Result<Entry> {
1213 match self.get_case_unfolded_name(parent, name)? {
1214 None => Err(io::Error::from_raw_os_error(libc::ENOENT)),
1215 Some(actual_name) => self.do_lookup(parent, &actual_name),
1216 }
1217 }
1218
1219 #[cfg(test)]
1220 fn exists_in_casefold_cache(&self, parent: Inode, name: &CStr) -> bool {
1221 let mut cache = self
1222 .lock_casefold_lookup_caches()
1223 .expect("casefold must be enabled");
1224 cache.exists_in_cache(parent, name)
1225 }
1226
1227 fn do_lookup(&self, parent: &InodeData, name: &CStr) -> io::Result<Entry> {
1228 let path_file = safe_openat2(
1229 parent,
1230 name,
1231 libc::O_PATH | libc::O_CLOEXEC | libc::O_NOFOLLOW,
1232 None,
1233 RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS,
1234 )?;
1235
1236 #[allow(unused_mut)]
1237 let mut st = stat(&path_file)?;
1238
1239 let altkey = InodeAltKey {
1240 ino: st.st_ino,
1241 dev: st.st_dev,
1242 };
1243
1244 let path = format!(
1245 "{}/{}",
1246 parent.path.clone(),
1247 name.to_str().unwrap_or("<non UTF-8 str>")
1248 );
1249
1250 if let Some(data) = self.inodes.lock().get_alt(&altkey) {
1252 #[cfg(feature = "arc_quota")]
1254 self.set_permission(&mut st, &path);
1255 #[cfg(feature = "fs_runtime_ugid_map")]
1256 self.set_ugid_permission(&mut st, &path);
1257 return Ok(Entry {
1258 inode: self.increase_inode_refcount(data),
1259 generation: 0,
1260 attr: st,
1261 attr_timeout: self.cfg.timeout,
1263 entry_timeout: self.cfg.timeout,
1264 });
1265 }
1266
1267 let mut flags = libc::O_RDONLY | libc::O_CLOEXEC;
1271 match FileType::from(st.st_mode) {
1272 FileType::Regular => {}
1273 FileType::Directory => flags |= libc::O_DIRECTORY,
1274 FileType::Other => flags |= libc::O_PATH,
1275 };
1276
1277 let pathname = CString::new(format!("self/fd/{}", path_file.as_raw_descriptor()))
1280 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1281
1282 let fd = match syscall!(unsafe {
1284 libc::openat64(self.proc.as_raw_descriptor(), pathname.as_ptr(), flags)
1285 }) {
1286 Ok(fd) => fd,
1287 Err(e) if e.errno() == libc::EACCES => {
1288 flags |= libc::O_PATH;
1290 syscall!(unsafe {
1292 libc::openat64(self.proc.as_raw_descriptor(), pathname.as_ptr(), flags)
1293 })?
1294 }
1295 Err(e) => return Err(e.into()),
1296 };
1297
1298 let f = unsafe { File::from_raw_descriptor(fd) };
1300 flags |= libc::O_NOFOLLOW;
1301 Ok(self.add_entry(f, st, flags, path))
1302 }
1303
1304 fn get_cache_open_options(&self, flags: u32) -> OpenOptions {
1305 let mut opts = OpenOptions::empty();
1306 match self.cfg.cache_policy {
1307 CachePolicy::Never => opts.set(
1309 OpenOptions::DIRECT_IO,
1310 flags & (libc::O_DIRECTORY as u32) == 0,
1311 ),
1312 CachePolicy::Always => {
1313 opts |= if flags & (libc::O_DIRECTORY as u32) == 0 {
1314 OpenOptions::KEEP_CACHE
1315 } else {
1316 OpenOptions::CACHE_DIR
1317 }
1318 }
1319 _ => {}
1320 };
1321 opts
1322 }
1323
1324 fn do_lookup_with_casefold_fallback(
1327 &self,
1328 parent: &InodeData,
1329 name: &CStr,
1330 ) -> io::Result<Entry> {
1331 let mut res = self.do_lookup(parent, name);
1332 if res.is_err() && self.cfg.ascii_casefold {
1334 res = self.ascii_casefold_lookup(parent, name.to_bytes());
1335 }
1336 res
1337 }
1338
1339 fn do_open(&self, inode: Inode, flags: u32) -> io::Result<(Option<Handle>, OpenOptions)> {
1340 let inode_data = self.find_inode(inode)?;
1341
1342 let open_flags = self.update_open_flags(flags as i32);
1343 let file = self.open_fd(inode_data.as_raw_descriptor(), open_flags)?;
1344
1345 let handle = self.next_handle.fetch_add(1, Ordering::Relaxed);
1346 let data = HandleData {
1347 inode,
1348 file: Mutex::new(OpenedFile::new(file, open_flags)),
1349 unsafe_leak_fd: AtomicBool::new(false),
1350 };
1351
1352 self.handles.lock().insert(handle, Arc::new(data));
1353
1354 let opts = self.get_cache_open_options(open_flags as u32);
1355
1356 Ok((Some(handle), opts))
1357 }
1358
1359 fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
1360 let mut handles = self.handles.lock();
1361
1362 if let btree_map::Entry::Occupied(e) = handles.entry(handle) {
1363 if e.get().inode == inode {
1364 e.remove();
1367 return Ok(());
1368 }
1369 }
1370
1371 Err(ebadf())
1372 }
1373
1374 fn do_getattr(&self, inode: &InodeData) -> io::Result<(libc::stat64, Duration)> {
1375 #[allow(unused_mut)]
1376 let mut st = stat(inode)?;
1377
1378 #[cfg(feature = "arc_quota")]
1379 self.set_permission(&mut st, &inode.path);
1380 #[cfg(feature = "fs_runtime_ugid_map")]
1381 self.set_ugid_permission(&mut st, &inode.path);
1382 Ok((st, self.cfg.timeout))
1383 }
1384
1385 fn do_unlink(&self, parent: &InodeData, name: &CStr, flags: libc::c_int) -> io::Result<()> {
1386 if name.to_bytes().contains(&b'/') {
1387 return Err(io::Error::from_raw_os_error(libc::EINVAL));
1388 }
1389 syscall!(unsafe { libc::unlinkat(parent.as_raw_descriptor(), name.as_ptr(), flags) })?;
1391 Ok(())
1392 }
1393
1394 fn do_fsync<F: AsRawDescriptor>(&self, file: &F, datasync: bool) -> io::Result<()> {
1395 syscall!(unsafe {
1397 if datasync {
1398 libc::fdatasync(file.as_raw_descriptor())
1399 } else {
1400 libc::fsync(file.as_raw_descriptor())
1401 }
1402 })?;
1403
1404 Ok(())
1405 }
1406
1407 fn with_proc_chdir<F, T>(&self, f: F) -> T
1415 where
1416 F: FnOnce() -> T,
1417 {
1418 let root = self.find_inode(ROOT_ID).expect("failed to find root inode");
1419
1420 let _proc_lock = self.process_lock.lock();
1422 let proc_cwd = unsafe { libc::fchdir(self.proc.as_raw_descriptor()) };
1425 debug_assert_eq!(
1426 proc_cwd,
1427 0,
1428 "failed to fchdir to /proc: {}",
1429 io::Error::last_os_error()
1430 );
1431
1432 let res = f();
1433
1434 let root_cwd = unsafe { libc::fchdir(root.as_raw_descriptor()) };
1437 debug_assert_eq!(
1438 root_cwd,
1439 0,
1440 "failed to fchdir back to root directory: {}",
1441 io::Error::last_os_error()
1442 );
1443
1444 res
1445 }
1446
1447 fn do_getxattr(&self, inode: &InodeData, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
1448 let file = inode.file.lock();
1449 let o_path_file = (file.open_flags & libc::O_PATH) != 0;
1450 let res = if o_path_file {
1451 let path = CString::new(format!("self/fd/{}", file.as_raw_descriptor()))
1455 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1456
1457 self.with_proc_chdir(|| unsafe {
1459 libc::getxattr(
1460 path.as_ptr(),
1461 name.as_ptr(),
1462 value.as_mut_ptr() as *mut libc::c_void,
1463 value.len() as libc::size_t,
1464 )
1465 })
1466 } else {
1467 unsafe {
1470 libc::fgetxattr(
1471 file.as_raw_descriptor(),
1472 name.as_ptr(),
1473 value.as_mut_ptr() as *mut libc::c_void,
1474 value.len() as libc::size_t,
1475 )
1476 }
1477 };
1478
1479 if res < 0 {
1480 Err(io::Error::last_os_error())
1481 } else {
1482 Ok(res as usize)
1483 }
1484 }
1485
1486 fn get_encryption_policy_ex<R: io::Read>(
1487 &self,
1488 inode: Inode,
1489 handle: Handle,
1490 mut r: R,
1491 ) -> io::Result<IoctlReply> {
1492 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
1493 self.find_inode(inode)?
1494 } else {
1495 self.find_handle(handle, inode)?
1496 };
1497
1498 let mut arg = unsafe { MaybeUninit::<fscrypt_get_policy_ex_arg>::zeroed().assume_init() };
1500 r.read_exact(arg.policy_size.as_mut_bytes())?;
1501
1502 let policy_size = cmp::min(arg.policy_size, size_of::<fscrypt_policy>() as u64);
1503 arg.policy_size = policy_size;
1504
1505 let res =
1506 unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_GET_ENCRYPTION_POLICY_EX, &mut arg) };
1508 if res < 0 {
1509 Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
1510 } else {
1511 let len = size_of::<u64>() + arg.policy_size as usize;
1512 Ok(IoctlReply::Done(Ok(<&[u8]>::from(&arg)[..len].to_vec())))
1513 }
1514 }
1515
1516 fn get_fsxattr(&self, inode: Inode, handle: Handle) -> io::Result<IoctlReply> {
1517 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
1518 self.find_inode(inode)?
1519 } else {
1520 self.find_handle(handle, inode)?
1521 };
1522
1523 let mut buf = MaybeUninit::<fsxattr>::zeroed();
1524
1525 let res = unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_FSGETXATTR, buf.as_mut_ptr()) };
1527 if res < 0 {
1528 Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
1529 } else {
1530 let xattr = unsafe { buf.assume_init() };
1532 Ok(IoctlReply::Done(Ok(xattr.as_bytes().to_vec())))
1533 }
1534 }
1535
1536 fn set_fsxattr<R: io::Read>(
1537 &self,
1538 #[cfg_attr(not(feature = "arc_quota"), allow(unused_variables))] ctx: Context,
1539 inode: Inode,
1540 handle: Handle,
1541 mut r: R,
1542 ) -> io::Result<IoctlReply> {
1543 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
1544 self.find_inode(inode)?
1545 } else {
1546 self.find_handle(handle, inode)?
1547 };
1548
1549 let mut in_attr = fsxattr::new_zeroed();
1550 r.read_exact(in_attr.as_mut_bytes())?;
1551
1552 #[cfg(feature = "arc_quota")]
1553 let st = stat(&*data)?;
1554
1555 #[cfg(feature = "arc_quota")]
1556 let ctx_uid = self.lookup_host_uid(&ctx, inode);
1557
1558 #[cfg(feature = "arc_quota")]
1561 if ctx_uid == st.st_uid || self.cfg.privileged_quota_uids.contains(&ctx_uid) {
1562 let mut buf = MaybeUninit::<fsxattr>::zeroed();
1564 let res = unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_FSGETXATTR, buf.as_mut_ptr()) };
1566 if res < 0 {
1567 return Ok(IoctlReply::Done(Err(io::Error::last_os_error())));
1568 }
1569 let current_attr = unsafe { buf.assume_init() };
1571
1572 if current_attr.fsx_projid != in_attr.fsx_projid {
1575 let connection = self.dbus_connection.as_ref().unwrap().lock();
1576 let proxy = connection.with_proxy(
1577 "org.chromium.Spaced",
1578 "/org/chromium/Spaced",
1579 DEFAULT_DBUS_TIMEOUT,
1580 );
1581 let project_id = in_attr.fsx_projid;
1582 if !is_android_project_id(project_id) {
1583 return Err(io::Error::from_raw_os_error(libc::EINVAL));
1584 }
1585 let file_clone = base::SafeDescriptor::try_from(&*data)?;
1586 match proxy.set_project_id(file_clone.into(), project_id) {
1587 Ok(r) => {
1588 let r = SetProjectIdReply::parse_from_bytes(&r)
1589 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1590 if !r.success {
1591 return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
1592 r.error,
1593 ))));
1594 }
1595 }
1596 Err(e) => {
1597 return Err(io::Error::other(e));
1598 }
1599 };
1600 }
1601 }
1602
1603 let res = unsafe { ioctl_with_ptr(&*data, FS_IOC_FSSETXATTR, &in_attr) };
1605 if res < 0 {
1606 Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
1607 } else {
1608 Ok(IoctlReply::Done(Ok(Vec::new())))
1609 }
1610 }
1611
1612 fn get_flags(&self, inode: Inode, handle: Handle) -> io::Result<IoctlReply> {
1613 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
1614 self.find_inode(inode)?
1615 } else {
1616 self.find_handle(handle, inode)?
1617 };
1618
1619 let mut flags: c_int = 0;
1621
1622 let res = unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_GETFLAGS, &mut flags) };
1624 if res < 0 {
1625 Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
1626 } else {
1627 Ok(IoctlReply::Done(Ok(flags.to_ne_bytes().to_vec())))
1628 }
1629 }
1630
1631 fn set_flags<R: io::Read>(
1632 &self,
1633 #[cfg_attr(not(feature = "arc_quota"), allow(unused_variables))] ctx: Context,
1634 inode: Inode,
1635 handle: Handle,
1636 mut r: R,
1637 ) -> io::Result<IoctlReply> {
1638 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
1639 self.find_inode(inode)?
1640 } else {
1641 self.find_handle(handle, inode)?
1642 };
1643
1644 let mut in_flags: c_int = 0;
1646 r.read_exact(in_flags.as_mut_bytes())?;
1647
1648 #[cfg(feature = "arc_quota")]
1649 let st = stat(&*data)?;
1650
1651 #[cfg(feature = "arc_quota")]
1652 let ctx_uid = self.lookup_host_uid(&ctx, inode);
1653
1654 #[cfg(feature = "arc_quota")]
1656 if ctx_uid == st.st_uid || self.cfg.privileged_quota_uids.contains(&ctx_uid) {
1657 let mut buf = MaybeUninit::<c_int>::zeroed();
1659 let res = unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_GETFLAGS, buf.as_mut_ptr()) };
1661 if res < 0 {
1662 return Ok(IoctlReply::Done(Err(io::Error::last_os_error())));
1663 }
1664 let current_flags = unsafe { buf.assume_init() };
1666
1667 if (in_flags & FS_PROJINHERIT_FL) != (current_flags & FS_PROJINHERIT_FL) {
1670 let connection = self.dbus_connection.as_ref().unwrap().lock();
1671 let proxy = connection.with_proxy(
1672 "org.chromium.Spaced",
1673 "/org/chromium/Spaced",
1674 DEFAULT_DBUS_TIMEOUT,
1675 );
1676 let enable = (in_flags & FS_PROJINHERIT_FL) == FS_PROJINHERIT_FL;
1679 let file_clone = base::SafeDescriptor::try_from(&*data)?;
1680 match proxy.set_project_inheritance_flag(file_clone.into(), enable) {
1681 Ok(r) => {
1682 let r = SetProjectInheritanceFlagReply::parse_from_bytes(&r)
1683 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1684 if !r.success {
1685 return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
1686 r.error,
1687 ))));
1688 }
1689 }
1690 Err(e) => {
1691 return Err(io::Error::other(e));
1692 }
1693 };
1694 }
1695 }
1696
1697 let res = unsafe { ioctl_with_ptr(&*data, FS_IOC_SETFLAGS, &in_flags) };
1699 if res < 0 {
1700 Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
1701 } else {
1702 Ok(IoctlReply::Done(Ok(Vec::new())))
1703 }
1704 }
1705
1706 fn enable_verity<R: io::Read>(
1707 &self,
1708 inode: Inode,
1709 handle: Handle,
1710 mut r: R,
1711 ) -> io::Result<IoctlReply> {
1712 let inode_data = self.find_inode(inode)?;
1713
1714 match inode_data.filetype {
1716 FileType::Regular => {}
1717 FileType::Directory => return Err(io::Error::from_raw_os_error(libc::EISDIR)),
1718 FileType::Other => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
1719 }
1720
1721 {
1722 let mut file = inode_data.file.lock();
1724 let mut flags = file.open_flags;
1725 match flags & libc::O_ACCMODE {
1726 libc::O_WRONLY | libc::O_RDWR => {
1727 flags &= !libc::O_ACCMODE;
1728 flags |= libc::O_RDONLY;
1729
1730 let newfile = self.open_fd(file.as_raw_descriptor(), libc::O_RDONLY)?;
1732 *file = OpenedFile::new(newfile, flags);
1733 }
1734 libc::O_RDONLY => {}
1735 _ => panic!("Unexpected flags: {flags:#x}"),
1736 }
1737 }
1738
1739 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
1740 inode_data
1741 } else {
1742 let data = self.find_handle(handle, inode)?;
1743
1744 {
1745 let mut file = data.file.lock();
1750 let flags = FileFlags::from_file(&*file).map_err(io::Error::from)?;
1751 match flags {
1752 FileFlags::ReadWrite | FileFlags::Write => {
1753 *file = OpenedFile::new(
1755 self.open_fd(file.as_raw_descriptor(), libc::O_RDONLY)?,
1756 libc::O_RDONLY,
1757 );
1758 }
1759 FileFlags::Read => {}
1760 }
1761 }
1762
1763 data
1764 };
1765
1766 let mut arg = fsverity_enable_arg::new_zeroed();
1767 r.read_exact(arg.as_mut_bytes())?;
1768
1769 let mut salt;
1770 if arg.salt_size > 0 {
1771 if arg.salt_size > self.max_buffer_size() {
1772 return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
1773 libc::ENOMEM,
1774 ))));
1775 }
1776 salt = vec![0; arg.salt_size as usize];
1777 r.read_exact(&mut salt)?;
1778 arg.salt_ptr = salt.as_ptr() as usize as u64;
1779 } else {
1780 arg.salt_ptr = 0;
1781 }
1782
1783 let mut sig;
1784 if arg.sig_size > 0 {
1785 if arg.sig_size > self.max_buffer_size() {
1786 return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
1787 libc::ENOMEM,
1788 ))));
1789 }
1790 sig = vec![0; arg.sig_size as usize];
1791 r.read_exact(&mut sig)?;
1792 arg.sig_ptr = sig.as_ptr() as usize as u64;
1793 } else {
1794 arg.sig_ptr = 0;
1795 }
1796
1797 let res = unsafe { ioctl_with_ptr(&*data, FS_IOC_ENABLE_VERITY, &arg) };
1799 if res < 0 {
1800 Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
1801 } else {
1802 Ok(IoctlReply::Done(Ok(Vec::new())))
1803 }
1804 }
1805
1806 fn measure_verity<R: io::Read>(
1807 &self,
1808 inode: Inode,
1809 handle: Handle,
1810 mut r: R,
1811 out_size: u32,
1812 ) -> io::Result<IoctlReply> {
1813 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
1814 self.find_inode(inode)?
1815 } else {
1816 self.find_handle(handle, inode)?
1817 };
1818
1819 let mut digest = fsverity_digest::new_zeroed();
1820 r.read_exact(digest.as_mut_bytes())?;
1821
1822 const FS_VERITY_MAX_DIGEST_SIZE: u16 = 64;
1824
1825 const DIGEST_SIZE: u16 = FS_VERITY_MAX_DIGEST_SIZE * 2 + 1;
1827 const BUFLEN: usize = size_of::<fsverity_digest>() + DIGEST_SIZE as usize;
1828 const ROUNDED_LEN: usize = BUFLEN.div_ceil(size_of::<fsverity_digest>());
1829
1830 let mut buf = [MaybeUninit::<fsverity_digest>::uninit(); ROUNDED_LEN];
1832
1833 unsafe {
1835 addr_of_mut!((*(buf.as_mut_ptr() as *mut fsverity_digest)).digest_size)
1837 .write(DIGEST_SIZE)
1838 };
1839
1840 let res = unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_MEASURE_VERITY, buf.as_mut_ptr()) };
1842 if res < 0 {
1843 Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
1844 } else {
1845 let digest_size =
1846 unsafe { addr_of!((*(buf.as_ptr() as *const fsverity_digest)).digest_size).read() };
1849 let outlen = size_of::<fsverity_digest>() as u32 + u32::from(digest_size);
1850
1851 debug_assert!(outlen <= (ROUNDED_LEN * size_of::<fsverity_digest>()) as u32);
1853 if digest.digest_size < digest_size || out_size < outlen {
1854 return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
1855 libc::EOVERFLOW,
1856 ))));
1857 }
1858
1859 let buf: [MaybeUninit<u8>; ROUNDED_LEN * size_of::<fsverity_digest>()] =
1860 unsafe { mem::transmute(buf) };
1863
1864 let buf =
1865 unsafe { &*(&buf[..outlen as usize] as *const [MaybeUninit<u8>] as *const [u8]) };
1870 Ok(IoctlReply::Done(Ok(buf.to_vec())))
1871 }
1872 }
1873}
1874
1875#[cfg(feature = "fs_runtime_ugid_map")]
1876impl PassthroughFs {
1877 fn find_and_set_ugid_permission(
1878 &self,
1879 st: &mut libc::stat64,
1880 path: &str,
1881 is_root_path: bool,
1882 ) -> bool {
1883 for perm_data in self
1884 .permission_paths
1885 .read()
1886 .expect("acquire permission_paths read lock")
1887 .iter()
1888 {
1889 if (is_root_path && perm_data.perm_path == "/")
1890 || (!is_root_path
1891 && perm_data.perm_path != "/"
1892 && perm_data.need_set_permission(path))
1893 {
1894 self.set_permission_from_data(st, perm_data);
1895 return true;
1896 }
1897 }
1898 false
1899 }
1900
1901 fn set_permission_from_data(&self, st: &mut libc::stat64, perm_data: &PermissionData) {
1902 st.st_uid = perm_data.guest_uid;
1903 st.st_gid = perm_data.guest_gid;
1904 st.st_mode = (st.st_mode & libc::S_IFMT) | (0o777 & !perm_data.umask);
1905 }
1906
1907 fn set_ugid_permission(&self, st: &mut libc::stat64, path: &str) {
1909 let is_root_path = path.is_empty();
1910
1911 if self.find_and_set_ugid_permission(st, path, is_root_path) {
1912 return;
1913 }
1914
1915 if let Some(perm_data) = self
1916 .permission_paths
1917 .read()
1918 .expect("acquire permission_paths read lock")
1919 .iter()
1920 .find(|pd| pd.perm_path == "/")
1921 {
1922 self.set_permission_from_data(st, perm_data);
1923 }
1924 }
1925
1926 fn change_ugid_creds(&self, ctx: &Context, parent_data: &InodeData, name: &CStr) -> (u32, u32) {
1928 let path = format!(
1929 "{}/{}",
1930 parent_data.path.clone(),
1931 name.to_str().unwrap_or("<non UTF-8 str>")
1932 );
1933
1934 self.change_ugid_creds_for_path(ctx, &path)
1935 }
1936
1937 fn change_ugid_creds_for_path(&self, ctx: &Context, path: &str) -> (u32, u32) {
1939 let is_root_path = path.is_empty();
1940
1941 if let Some(creds) = self.find_ugid_creds_for_path(path, is_root_path) {
1942 return creds;
1943 }
1944
1945 if let Some(perm_data) = self
1946 .permission_paths
1947 .read()
1948 .expect("acquire permission_paths read lock")
1949 .iter()
1950 .find(|pd| pd.perm_path == "/")
1951 {
1952 return (perm_data.host_uid, perm_data.host_gid);
1953 }
1954
1955 (ctx.uid, ctx.gid)
1956 }
1957
1958 fn find_ugid_creds_for_path(&self, path: &str, is_root_path: bool) -> Option<(u32, u32)> {
1959 for perm_data in self
1960 .permission_paths
1961 .read()
1962 .expect("acquire permission_paths read lock")
1963 .iter()
1964 {
1965 if (is_root_path && perm_data.perm_path == "/")
1966 || (!is_root_path
1967 && perm_data.perm_path != "/"
1968 && perm_data.need_set_permission(path))
1969 {
1970 return Some((perm_data.host_uid, perm_data.host_gid));
1971 }
1972 }
1973 None
1974 }
1975}
1976
1977#[cfg(feature = "arc_quota")]
1978impl PassthroughFs {
1979 fn string_from_u8_slice(&self, buf: &[u8]) -> io::Result<String> {
1981 match CStr::from_bytes_until_nul(buf).map(|s| s.to_string_lossy().to_string()) {
1982 Ok(s) => Ok(s),
1983 Err(e) => {
1984 error!("fail to convert u8 slice to string: {}", e);
1985 Err(io::Error::from_raw_os_error(libc::EINVAL))
1986 }
1987 }
1988 }
1989
1990 fn set_permission(&self, st: &mut libc::stat64, path: &str) {
1992 for perm_data in self
1993 .permission_paths
1994 .read()
1995 .expect("acquire permission_paths read lock")
1996 .iter()
1997 {
1998 if perm_data.need_set_permission(path) {
1999 st.st_uid = perm_data.guest_uid;
2000 st.st_gid = perm_data.guest_gid;
2001 st.st_mode = (st.st_mode & libc::S_IFMT) | (0o777 & !perm_data.umask);
2002 }
2003 }
2004 }
2005
2006 fn change_creds(&self, ctx: &Context, parent_data: &InodeData, name: &CStr) -> (u32, u32) {
2008 let path = format!(
2009 "{}/{}",
2010 parent_data.path.clone(),
2011 name.to_str().unwrap_or("<non UTF-8 str>")
2012 );
2013
2014 self.change_creds_for_path(ctx, &path)
2015 }
2016
2017 fn change_creds_for_path(&self, ctx: &Context, path: &str) -> (u32, u32) {
2019 for perm_data in self
2020 .permission_paths
2021 .read()
2022 .expect("acquire permission_paths read lock")
2023 .iter()
2024 {
2025 if perm_data.need_set_permission(path) {
2026 return (perm_data.host_uid, perm_data.host_gid);
2027 }
2028 }
2029
2030 (ctx.uid, ctx.gid)
2031 }
2032
2033 fn read_permission_data<R: io::Read>(&self, mut r: R) -> io::Result<PermissionData> {
2034 let mut fs_permission_data = FsPermissionDataBuffer::new_zeroed();
2035 r.read_exact(fs_permission_data.as_mut_bytes())?;
2036
2037 let perm_path = self.string_from_u8_slice(&fs_permission_data.perm_path)?;
2038 if !perm_path.starts_with('/') {
2039 error!("FS_IOC_SETPERMISSION: perm path must start with '/'");
2040 return Err(io::Error::from_raw_os_error(libc::EINVAL));
2041 }
2042 Ok(PermissionData {
2043 guest_uid: fs_permission_data.guest_uid,
2044 guest_gid: fs_permission_data.guest_gid,
2045 host_uid: fs_permission_data.host_uid,
2046 host_gid: fs_permission_data.host_gid,
2047 umask: fs_permission_data.umask,
2048 perm_path,
2049 })
2050 }
2051
2052 fn set_permission_by_path<R: io::Read>(&self, r: R) -> IoctlReply {
2076 if self
2077 .permission_paths
2078 .read()
2079 .expect("acquire permission_paths read lock")
2080 .len()
2081 >= self.cfg.max_dynamic_perm
2082 {
2083 error!(
2084 "FS_IOC_SETPERMISSION exceeds limits of max_dynamic_perm: {}",
2085 self.cfg.max_dynamic_perm
2086 );
2087 return IoctlReply::Done(Err(io::Error::from_raw_os_error(libc::EPERM)));
2088 }
2089
2090 let perm_data = match self.read_permission_data(r) {
2091 Ok(data) => data,
2092 Err(e) => {
2093 error!("fail to read permission data: {}", e);
2094 return IoctlReply::Done(Err(e));
2095 }
2096 };
2097
2098 self.permission_paths
2099 .write()
2100 .expect("acquire permission_paths write lock")
2101 .push(perm_data);
2102
2103 IoctlReply::Done(Ok(Vec::new()))
2104 }
2105
2106 fn get_xattr_by_path(&self, path: &str, name: &str) -> Option<String> {
2108 self.xattr_paths
2109 .read()
2110 .expect("acquire permission_paths read lock")
2111 .iter()
2112 .find(|data| data.need_set_guest_xattr(path, name))
2113 .map(|data| data.xattr_value.clone())
2114 }
2115
2116 fn skip_host_set_xattr(&self, path: &str, name: &str) -> bool {
2117 self.get_xattr_by_path(path, name).is_some()
2118 }
2119
2120 fn read_xattr_data<R: io::Read>(&self, mut r: R) -> io::Result<XattrData> {
2121 let mut fs_path_xattr_data = FsPathXattrDataBuffer::new_zeroed();
2122 r.read_exact(fs_path_xattr_data.as_mut_bytes())?;
2123
2124 let xattr_path = self.string_from_u8_slice(&fs_path_xattr_data.path)?;
2125 if !xattr_path.starts_with('/') {
2126 error!("FS_IOC_SETPATHXATTR: perm path must start with '/'");
2127 return Err(io::Error::from_raw_os_error(libc::EINVAL));
2128 }
2129 let xattr_name = self.string_from_u8_slice(&fs_path_xattr_data.xattr_name)?;
2130 let xattr_value = self.string_from_u8_slice(&fs_path_xattr_data.xattr_value)?;
2131
2132 Ok(XattrData {
2133 xattr_path,
2134 xattr_name,
2135 xattr_value,
2136 })
2137 }
2138
2139 fn set_xattr_by_path<R: io::Read>(&self, r: R) -> IoctlReply {
2153 if self
2154 .xattr_paths
2155 .read()
2156 .expect("acquire xattr_paths read lock")
2157 .len()
2158 >= self.cfg.max_dynamic_xattr
2159 {
2160 error!(
2161 "FS_IOC_SETPATHXATTR exceeds limits of max_dynamic_xattr: {}",
2162 self.cfg.max_dynamic_xattr
2163 );
2164 return IoctlReply::Done(Err(io::Error::from_raw_os_error(libc::EPERM)));
2165 }
2166
2167 let xattr_data = match self.read_xattr_data(r) {
2168 Ok(data) => data,
2169 Err(e) => {
2170 error!("fail to read xattr data: {}", e);
2171 return IoctlReply::Done(Err(e));
2172 }
2173 };
2174
2175 self.xattr_paths
2176 .write()
2177 .expect("acquire xattr_paths write lock")
2178 .push(xattr_data);
2179
2180 IoctlReply::Done(Ok(Vec::new()))
2181 }
2182
2183 fn do_getxattr_with_filter(
2184 &self,
2185 data: Arc<InodeData>,
2186 name: Cow<CStr>,
2187 buf: &mut [u8],
2188 ) -> io::Result<usize> {
2189 let res: usize = match self.get_xattr_by_path(&data.path, &name.to_string_lossy()) {
2190 Some(predifined_xattr) => {
2191 let x = predifined_xattr.into_bytes();
2192 if x.len() > buf.len() {
2193 return Err(io::Error::from_raw_os_error(libc::ERANGE));
2194 }
2195 buf[..x.len()].copy_from_slice(&x);
2196 x.len()
2197 }
2198 None => self.do_getxattr(&data, &name, &mut buf[..])?,
2199 };
2200 Ok(res)
2201 }
2202
2203 fn lookup_host_uid(&self, ctx: &Context, inode: Inode) -> u32 {
2205 if let Ok(inode_data) = self.find_inode(inode) {
2206 let path = &inode_data.path;
2207 for perm_data in self
2208 .permission_paths
2209 .read()
2210 .expect("acquire permission_paths read lock")
2211 .iter()
2212 {
2213 if perm_data.need_set_permission(path) {
2214 return perm_data.host_uid;
2215 }
2216 }
2217 }
2218 ctx.uid
2219 }
2220}
2221
2222fn forget_one(
2225 inodes: &mut MultikeyBTreeMap<Inode, InodeAltKey, Arc<InodeData>>,
2226 inode: Inode,
2227 count: u64,
2228) -> bool {
2229 if let Some(data) = inodes.get(&inode) {
2230 loop {
2235 let refcount = data.refcount.load(Ordering::Relaxed);
2236
2237 let new_count = refcount.saturating_sub(count);
2240
2241 if data
2243 .refcount
2244 .compare_exchange_weak(refcount, new_count, Ordering::Release, Ordering::Relaxed)
2245 .is_ok()
2246 {
2247 if new_count == 0 {
2248 inodes.remove(&inode);
2254 return true;
2255 }
2256 break;
2257 }
2258 }
2259 }
2260 false
2261}
2262
2263fn strip_xattr_prefix(buf: &mut Vec<u8>) {
2266 fn next_cstr(b: &[u8], start: usize) -> Option<&[u8]> {
2267 if start >= b.len() {
2268 return None;
2269 }
2270
2271 let end = b[start..]
2272 .iter()
2273 .position(|&c| c == b'\0')
2274 .map(|p| start + p + 1)
2275 .unwrap_or(b.len());
2276
2277 Some(&b[start..end])
2278 }
2279
2280 let mut pos = 0;
2281 while let Some(name) = next_cstr(buf, pos) {
2282 if !name.starts_with(USER_VIRTIOFS_XATTR) {
2283 pos += name.len();
2284 continue;
2285 }
2286
2287 let newlen = name.len() - USER_VIRTIOFS_XATTR.len();
2288 buf.drain(pos..pos + USER_VIRTIOFS_XATTR.len());
2289 pos += newlen;
2290 }
2291}
2292
2293impl Drop for PassthroughFs {
2294 fn drop(&mut self) {
2303 let inodes = self.inodes.lock();
2304 inodes.apply(|v| {
2305 v.set_unsafe_leak_fd();
2306 });
2307 let handles = self.handles.lock();
2308 handles.values().for_each(|v| v.set_unsafe_leak_fd());
2309 }
2310}
2311
2312impl FileSystem for PassthroughFs {
2313 type Inode = Inode;
2314 type Handle = Handle;
2315 type DirIter = ReadDir<Box<[u8]>>;
2316
2317 fn init(&self, capable: FsOptions) -> io::Result<FsOptions> {
2318 let root = CString::new(self.root_dir.clone())
2319 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
2320
2321 let flags = libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC;
2322 let raw_descriptor = unsafe { libc::openat64(libc::AT_FDCWD, root.as_ptr(), flags) };
2324 if raw_descriptor < 0 {
2325 return Err(io::Error::last_os_error());
2326 }
2327
2328 let f = unsafe { File::from_raw_descriptor(raw_descriptor) };
2330
2331 let st = stat(&f)?;
2332
2333 unsafe { libc::umask(0o000) };
2337
2338 let mut inodes = self.inodes.lock();
2339
2340 inodes.insert(
2342 ROOT_ID,
2343 InodeAltKey {
2344 ino: st.st_ino,
2345 dev: st.st_dev,
2346 },
2347 Arc::new(InodeData {
2348 inode: ROOT_ID,
2349 file: Mutex::new(OpenedFile::new(f, flags)),
2350 refcount: AtomicU64::new(2),
2351 filetype: st.st_mode.into(),
2352 path: "".to_string(),
2353 unsafe_leak_fd: AtomicBool::new(false),
2354 }),
2355 );
2356
2357 let mut opts = FsOptions::DO_READDIRPLUS
2358 | FsOptions::READDIRPLUS_AUTO
2359 | FsOptions::EXPORT_SUPPORT
2360 | FsOptions::DONT_MASK
2361 | FsOptions::CACHE_SYMLINKS;
2362
2363 if self.cfg.max_dynamic_xattr == 0 && self.cfg.security_ctx {
2367 opts |= FsOptions::SECURITY_CONTEXT;
2368 }
2369
2370 if self.cfg.posix_acl {
2371 opts |= FsOptions::POSIX_ACL;
2372 }
2373 if self.cfg.writeback && capable.contains(FsOptions::WRITEBACK_CACHE) {
2374 opts |= FsOptions::WRITEBACK_CACHE;
2375 self.writeback.store(true, Ordering::Relaxed);
2376 }
2377 if self.cfg.cache_policy == CachePolicy::Always {
2378 if capable.contains(FsOptions::ZERO_MESSAGE_OPEN) {
2379 opts |= FsOptions::ZERO_MESSAGE_OPEN;
2380 self.zero_message_open.store(true, Ordering::Relaxed);
2381 }
2382 if capable.contains(FsOptions::ZERO_MESSAGE_OPENDIR) {
2383 opts |= FsOptions::ZERO_MESSAGE_OPENDIR;
2384 self.zero_message_opendir.store(true, Ordering::Relaxed);
2385 }
2386 }
2387 Ok(opts)
2388 }
2389
2390 fn destroy(&self) {
2391 cros_tracing::trace_simple_print!(VirtioFs, "{:?}: destroy", self);
2392 self.handles.lock().clear();
2393 self.inodes.lock().clear();
2394 }
2395
2396 fn statfs(&self, _ctx: Context, inode: Inode) -> io::Result<libc::statvfs64> {
2397 let _trace = fs_trace!(self.tag, "statfs", inode);
2398 let data = self.find_inode(inode)?;
2399
2400 let mut out = MaybeUninit::<libc::statvfs64>::zeroed();
2401
2402 syscall!(unsafe { libc::fstatvfs64(data.as_raw_descriptor(), out.as_mut_ptr()) })?;
2404
2405 Ok(unsafe { out.assume_init() })
2407 }
2408
2409 fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
2410 validate_path_component(name)?;
2411 let data = self.find_inode(parent)?;
2412 #[allow(unused_variables)]
2413 let path = format!(
2414 "{}/{}",
2415 data.path,
2416 name.to_str().unwrap_or("<non UTF-8 path>")
2417 );
2418 let _trace = fs_trace!(self.tag, "lookup", parent, path);
2419
2420 if !self.is_path_accessible(&path) {
2421 return Err(io::Error::from_raw_os_error(libc::ENOENT));
2422 }
2423
2424 let mut res = self.do_lookup_with_casefold_fallback(&data, name);
2425
2426 if let Err(e) = &res {
2430 if e.kind() == std::io::ErrorKind::NotFound && !self.cfg.negative_timeout.is_zero() {
2431 res = Ok(Entry::new_negative(self.cfg.negative_timeout));
2432 }
2433 }
2434
2435 res
2436 }
2437
2438 fn forget(&self, _ctx: Context, inode: Inode, count: u64) {
2439 let _trace = fs_trace!(self.tag, "forget", inode, count);
2440 let mut inodes = self.inodes.lock();
2441 let caches = self.lock_casefold_lookup_caches();
2442 if forget_one(&mut inodes, inode, count) {
2443 if let Some(mut c) = caches {
2444 c.forget(inode);
2445 }
2446 }
2447 }
2448
2449 fn batch_forget(&self, _ctx: Context, requests: Vec<(Inode, u64)>) {
2450 let mut inodes = self.inodes.lock();
2451 let mut caches = self.lock_casefold_lookup_caches();
2452 for (inode, count) in requests {
2453 if forget_one(&mut inodes, inode, count) {
2454 if let Some(c) = caches.as_mut() {
2455 c.forget(inode);
2456 }
2457 }
2458 }
2459 }
2460
2461 fn opendir(
2462 &self,
2463 _ctx: Context,
2464 inode: Inode,
2465 flags: u32,
2466 ) -> io::Result<(Option<Handle>, OpenOptions)> {
2467 let _trace = fs_trace!(self.tag, "opendir", inode, flags);
2468 if self.zero_message_opendir.load(Ordering::Relaxed) {
2469 Err(io::Error::from_raw_os_error(libc::ENOSYS))
2470 } else {
2471 self.do_open(inode, flags | (libc::O_DIRECTORY as u32))
2472 }
2473 }
2474
2475 fn releasedir(
2476 &self,
2477 _ctx: Context,
2478 inode: Inode,
2479 _flags: u32,
2480 handle: Handle,
2481 ) -> io::Result<()> {
2482 let _trace = fs_trace!(self.tag, "releasedir", inode, handle);
2483 if self.zero_message_opendir.load(Ordering::Relaxed) {
2484 Ok(())
2485 } else {
2486 self.do_release(inode, handle)
2487 }
2488 }
2489
2490 fn mkdir(
2491 &self,
2492 ctx: Context,
2493 parent: Inode,
2494 name: &CStr,
2495 mode: u32,
2496 umask: u32,
2497 security_ctx: Option<&CStr>,
2498 ) -> io::Result<Entry> {
2499 let _trace = fs_trace!(self.tag, "mkdir", parent, name, mode, umask, security_ctx);
2500 let data = self.find_inode(parent)?;
2501 self.authorize_write_path(&data.path, name)?;
2502
2503 let _ctx = security_ctx
2504 .filter(|ctx| *ctx != UNLABELED_CSTR)
2505 .map(|ctx| ScopedSecurityContext::new(&self.proc, ctx))
2506 .transpose()?;
2507
2508 #[allow(unused_variables)]
2509 #[cfg(feature = "arc_quota")]
2510 let (uid, gid) = self.change_creds(&ctx, &data, name);
2511 #[cfg(feature = "fs_runtime_ugid_map")]
2512 let (uid, gid) = self.change_ugid_creds(&ctx, &data, name);
2513 #[cfg(not(feature = "fs_permission_translation"))]
2514 let (uid, gid) = (ctx.uid, ctx.gid);
2515
2516 let (_uid, _gid) = set_creds(uid, gid)?;
2517 {
2518 let casefold_cache = self.lock_casefold_lookup_caches();
2519 let _scoped_umask = ScopedUmask::new(umask);
2520
2521 syscall!(unsafe { libc::mkdirat(data.as_raw_descriptor(), name.as_ptr(), mode) })?;
2523 if let Some(mut c) = casefold_cache {
2524 c.insert(data.inode, name);
2525 }
2526 }
2527 self.do_lookup(&data, name)
2528 }
2529
2530 fn rmdir(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<()> {
2531 let _trace = fs_trace!(self.tag, "rmdir", parent, name);
2532 let data = self.find_inode(parent)?;
2533 self.authorize_write_path(&data.path, name)?;
2534 let casefold_cache = self.lock_casefold_lookup_caches();
2535 self.do_unlink(&data, name, libc::AT_REMOVEDIR)?;
2538 if let Some(mut c) = casefold_cache {
2539 c.remove(data.inode, name);
2540 }
2541 Ok(())
2542 }
2543
2544 fn readdir(
2545 &self,
2546 _ctx: Context,
2547 inode: Inode,
2548 handle: Handle,
2549 size: u32,
2550 offset: u64,
2551 ) -> io::Result<Self::DirIter> {
2552 let _trace = fs_trace!(self.tag, "readdir", inode, handle, size, offset);
2553 let buf = vec![0; size as usize].into_boxed_slice();
2554
2555 let (parent_path, mut read_dir) = if self.zero_message_opendir.load(Ordering::Relaxed) {
2559 let data = self.find_inode(inode)?;
2560 let path = data.path.clone();
2561 let dir_guard = data.file.lock();
2562 let read_dir = ReadDir::new(&*dir_guard, offset as libc::off64_t, buf)?;
2563 (path, read_dir)
2564 } else {
2565 let data = self.find_handle(handle, inode)?;
2566 let inode_data = self.find_inode(data.inode)?;
2567 let path = inode_data.path.clone();
2568 let dir_guard = data.file.lock();
2569 let read_dir = ReadDir::new(&*dir_guard, offset as libc::off64_t, buf)?;
2570 (path, read_dir)
2571 };
2572
2573 if let Some(allowlist) = &self.allowlist {
2576 let allowlist_guard = allowlist
2577 .read()
2578 .expect("failed to acquire read lock on allowlist");
2579 let filter = allowlist_guard.get_read_dir_filter(&parent_path);
2580 read_dir = read_dir.with_filter(filter);
2581 }
2582
2583 Ok(read_dir)
2584 }
2585
2586 fn open(
2587 &self,
2588 _ctx: Context,
2589 inode: Inode,
2590 flags: u32,
2591 ) -> io::Result<(Option<Handle>, OpenOptions)> {
2592 if self.zero_message_open.load(Ordering::Relaxed) {
2593 let _trace = fs_trace!(self.tag, "open (zero-message)", inode, flags);
2594 Err(io::Error::from_raw_os_error(libc::ENOSYS))
2595 } else {
2596 let _trace = fs_trace!(self.tag, "open", inode, flags);
2597 self.do_open(inode, flags)
2598 }
2599 }
2600
2601 fn release(
2602 &self,
2603 _ctx: Context,
2604 inode: Inode,
2605 _flags: u32,
2606 handle: Handle,
2607 _flush: bool,
2608 _flock_release: bool,
2609 _lock_owner: Option<u64>,
2610 ) -> io::Result<()> {
2611 if self.zero_message_open.load(Ordering::Relaxed) {
2612 let _trace = fs_trace!(self.tag, "release (zero-message)", inode, handle);
2613 Ok(())
2614 } else {
2615 let _trace = fs_trace!(self.tag, "release", inode, handle);
2616 self.do_release(inode, handle)
2617 }
2618 }
2619
2620 fn chromeos_tmpfile(
2621 &self,
2622 ctx: Context,
2623 parent: Self::Inode,
2624 mode: u32,
2625 umask: u32,
2626 security_ctx: Option<&CStr>,
2627 ) -> io::Result<Entry> {
2628 let _trace = fs_trace!(
2629 self.tag,
2630 "chromeos_tempfile",
2631 parent,
2632 mode,
2633 umask,
2634 security_ctx
2635 );
2636 let data = self.find_inode(parent)?;
2637
2638 let _ctx = security_ctx
2639 .filter(|ctx| *ctx != UNLABELED_CSTR)
2640 .map(|ctx| ScopedSecurityContext::new(&self.proc, ctx))
2641 .transpose()?;
2642
2643 let tmpflags = libc::O_RDWR | libc::O_TMPFILE | libc::O_CLOEXEC | libc::O_NOFOLLOW;
2644
2645 let current_dir = c".";
2646
2647 #[allow(unused_variables)]
2648 #[cfg(feature = "arc_quota")]
2649 let (uid, gid) = self.change_creds(&ctx, &data, current_dir);
2650 #[cfg(feature = "fs_runtime_ugid_map")]
2651 let (uid, gid) = self.change_ugid_creds(&ctx, &data, current_dir);
2652 #[cfg(not(feature = "fs_permission_translation"))]
2653 let (uid, gid) = (ctx.uid, ctx.gid);
2654
2655 let (_uid, _gid) = set_creds(uid, gid)?;
2656
2657 let fd = {
2658 let _scoped_umask = ScopedUmask::new(umask);
2659
2660 syscall!(unsafe {
2662 libc::openat64(
2663 data.as_raw_descriptor(),
2664 current_dir.as_ptr(),
2665 tmpflags,
2666 mode,
2667 )
2668 })?
2669 };
2670 let tmpfile = unsafe { File::from_raw_descriptor(fd) };
2674 let st = stat(&tmpfile)?;
2675 let path = format!(
2676 "{}/{}",
2677 data.path.clone(),
2678 current_dir.to_str().unwrap_or("<non UTF-8 str>")
2679 );
2680 Ok(self.add_entry(tmpfile, st, tmpflags, path))
2681 }
2682
2683 fn create(
2684 &self,
2685 ctx: Context,
2686 parent: Inode,
2687 name: &CStr,
2688 mode: u32,
2689 flags: u32,
2690 umask: u32,
2691 security_ctx: Option<&CStr>,
2692 ) -> io::Result<(Entry, Option<Handle>, OpenOptions)> {
2693 let _trace = fs_trace!(
2694 self.tag,
2695 "create",
2696 parent,
2697 name,
2698 mode,
2699 flags,
2700 umask,
2701 security_ctx
2702 );
2703 let data = self.find_inode(parent)?;
2704 let path = self.authorize_write_path(&data.path, name)?;
2705
2706 let _ctx = security_ctx
2707 .filter(|ctx| *ctx != UNLABELED_CSTR)
2708 .map(|ctx| ScopedSecurityContext::new(&self.proc, ctx))
2709 .transpose()?;
2710
2711 #[allow(unused_variables)]
2712 #[cfg(feature = "arc_quota")]
2713 let (uid, gid) = self.change_creds(&ctx, &data, name);
2714 #[cfg(feature = "fs_runtime_ugid_map")]
2715 let (uid, gid) = self.change_ugid_creds(&ctx, &data, name);
2716 #[cfg(not(feature = "fs_permission_translation"))]
2717 let (uid, gid) = (ctx.uid, ctx.gid);
2718
2719 let (_uid, _gid) = set_creds(uid, gid)?;
2720
2721 let flags = self.update_open_flags(flags as i32);
2722 let create_flags = (flags | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW)
2725 & !(libc::O_DIRECT | libc::O_PATH);
2726
2727 let file = {
2728 let _scoped_umask = ScopedUmask::new(umask);
2729 let casefold_cache = self.lock_casefold_lookup_caches();
2730
2731 let file = safe_openat2(
2732 &data,
2733 name,
2734 create_flags,
2735 Some(mode),
2736 RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS,
2737 )?;
2738 if let Some(mut c) = casefold_cache {
2739 c.insert(parent, name);
2740 }
2741 file
2742 };
2743
2744 let st = stat(&file)?;
2745 let entry = self.add_entry(file, st, create_flags, path);
2746
2747 let (handle, opts) = if self.zero_message_open.load(Ordering::Relaxed) {
2748 (None, OpenOptions::KEEP_CACHE)
2749 } else {
2750 self.do_open(
2751 entry.inode,
2752 flags as u32 & !((libc::O_CREAT | libc::O_EXCL | libc::O_NOCTTY) as u32),
2753 )
2754 .inspect_err(|_e| {
2755 self.forget(ctx, entry.inode, 1);
2757 })?
2758 };
2759 Ok((entry, handle, opts))
2760 }
2761
2762 fn unlink(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<()> {
2763 let _trace = fs_trace!(self.tag, "unlink", parent, name);
2764 let data = self.find_inode(parent)?;
2765 self.authorize_write_path(&data.path, name)?;
2766 let casefold_cache = self.lock_casefold_lookup_caches();
2767 self.do_unlink(&data, name, 0)?;
2770 if let Some(mut c) = casefold_cache {
2771 c.remove(data.inode, name);
2772 }
2773 Ok(())
2774 }
2775
2776 fn read<W: io::Write + ZeroCopyWriter>(
2777 &self,
2778 _ctx: Context,
2779 inode: Inode,
2780 handle: Handle,
2781 mut w: W,
2782 size: u32,
2783 offset: u64,
2784 _lock_owner: Option<u64>,
2785 _flags: u32,
2786 ) -> io::Result<usize> {
2787 if self.zero_message_open.load(Ordering::Relaxed) {
2788 let _trace = fs_trace!(self.tag, "read (zero-message)", inode, handle, size, offset);
2789 let data = self.find_inode(inode)?;
2790
2791 let mut file = data.file.lock();
2792 let mut flags = file.open_flags;
2793 match flags & libc::O_ACCMODE {
2794 libc::O_WRONLY => {
2795 flags &= !libc::O_WRONLY;
2796 flags |= libc::O_RDWR;
2797
2798 let newfile = self.open_fd(file.as_raw_descriptor(), libc::O_RDWR)?;
2800 *file = OpenedFile::new(newfile, flags);
2801 }
2802 libc::O_RDONLY | libc::O_RDWR => {}
2803 _ => panic!("Unexpected flags: {flags:#x}"),
2804 }
2805
2806 w.write_from(file.file_mut(), size as usize, offset)
2807 } else {
2808 let _trace = fs_trace!(self.tag, "read", inode, handle, size, offset);
2809 let data = self.find_handle(handle, inode)?;
2810
2811 let mut f = data.file.lock();
2812 w.write_from(f.file_mut(), size as usize, offset)
2813 }
2814 }
2815
2816 fn write<R: io::Read + ZeroCopyReader>(
2817 &self,
2818 _ctx: Context,
2819 inode: Inode,
2820 handle: Handle,
2821 mut r: R,
2822 size: u32,
2823 offset: u64,
2824 _lock_owner: Option<u64>,
2825 _delayed_write: bool,
2826 flags: u32,
2827 ) -> io::Result<usize> {
2828 let _fsetid = if flags & WRITE_KILL_PRIV != 0 {
2831 Some(drop_cap_fsetid()?)
2832 } else {
2833 None
2834 };
2835
2836 if self.zero_message_open.load(Ordering::Relaxed) {
2837 let _trace = fs_trace!(
2838 self.tag,
2839 "write (zero-message)",
2840 inode,
2841 handle,
2842 size,
2843 offset
2844 );
2845
2846 let data = self.find_inode(inode)?;
2847
2848 let mut file = data.file.lock();
2849 let mut flags = file.open_flags;
2850 match flags & libc::O_ACCMODE {
2851 libc::O_RDONLY => {
2852 flags &= !libc::O_RDONLY;
2853 flags |= libc::O_RDWR;
2854
2855 let newfile = self.open_fd(file.as_raw_descriptor(), libc::O_RDWR)?;
2857 *file = OpenedFile::new(newfile, flags);
2858 }
2859 libc::O_WRONLY | libc::O_RDWR => {}
2860 _ => panic!("Unexpected flags: {flags:#x}"),
2861 }
2862
2863 r.read_to(file.file_mut(), size as usize, offset)
2864 } else {
2865 let _trace = fs_trace!(self.tag, "write", inode, handle, size, offset);
2866
2867 let data = self.find_handle(handle, inode)?;
2868
2869 let mut f = data.file.lock();
2870 r.read_to(f.file_mut(), size as usize, offset)
2871 }
2872 }
2873
2874 fn getattr(
2875 &self,
2876 _ctx: Context,
2877 inode: Inode,
2878 _handle: Option<Handle>,
2879 ) -> io::Result<(libc::stat64, Duration)> {
2880 let _trace = fs_trace!(self.tag, "getattr", inode, _handle);
2881
2882 let data = self.find_inode(inode)?;
2883 self.do_getattr(&data)
2884 }
2885
2886 fn setattr(
2887 &self,
2888 _ctx: Context,
2889 inode: Inode,
2890 attr: libc::stat64,
2891 handle: Option<Handle>,
2892 valid: SetattrValid,
2893 ) -> io::Result<(libc::stat64, Duration)> {
2894 let _trace = fs_trace!(self.tag, "setattr", inode, handle);
2895 let inode_data = self.find_inode(inode)?;
2896
2897 enum Data<'a> {
2898 Handle(MutexGuard<'a, OpenedFile>),
2899 ProcPath(CString),
2900 }
2901
2902 let hd;
2904 let data = if let Some(handle) = handle.filter(|&h| h != 0) {
2905 hd = self.find_handle(handle, inode)?;
2906 Data::Handle(hd.file.lock())
2907 } else {
2908 let pathname = CString::new(format!("self/fd/{}", inode_data.as_raw_descriptor()))
2909 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
2910 Data::ProcPath(pathname)
2911 };
2912
2913 if valid.contains(SetattrValid::MODE) {
2914 syscall!(unsafe {
2916 match data {
2917 Data::Handle(ref fd) => libc::fchmod(fd.as_raw_descriptor(), attr.st_mode),
2918 Data::ProcPath(ref p) => {
2919 libc::fchmodat(self.proc.as_raw_descriptor(), p.as_ptr(), attr.st_mode, 0)
2920 }
2921 }
2922 })?;
2923 }
2924
2925 if valid.intersects(SetattrValid::UID | SetattrValid::GID) {
2926 let uid = if valid.contains(SetattrValid::UID) {
2927 attr.st_uid
2928 } else {
2929 u32::MAX
2931 };
2932 let gid = if valid.contains(SetattrValid::GID) {
2933 attr.st_gid
2934 } else {
2935 u32::MAX
2937 };
2938
2939 syscall!(unsafe {
2941 libc::fchownat(
2942 inode_data.as_raw_descriptor(),
2943 EMPTY_CSTR.as_ptr(),
2944 uid,
2945 gid,
2946 libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW,
2947 )
2948 })?;
2949 }
2950
2951 if valid.contains(SetattrValid::SIZE) {
2952 syscall!(match data {
2953 Data::Handle(ref fd) => {
2954 unsafe { libc::ftruncate64(fd.as_raw_descriptor(), attr.st_size) }
2956 }
2957 _ => {
2958 let f = self.open_inode(&inode_data, libc::O_NONBLOCK | libc::O_RDWR)?;
2960 unsafe { libc::ftruncate64(f.as_raw_descriptor(), attr.st_size) }
2962 }
2963 })?;
2964 }
2965
2966 if valid.intersects(SetattrValid::ATIME | SetattrValid::MTIME) {
2967 let mut tvs = [
2968 libc::timespec {
2969 tv_sec: 0,
2970 tv_nsec: libc::UTIME_OMIT,
2971 },
2972 libc::timespec {
2973 tv_sec: 0,
2974 tv_nsec: libc::UTIME_OMIT,
2975 },
2976 ];
2977
2978 if valid.contains(SetattrValid::ATIME_NOW) {
2979 tvs[0].tv_nsec = libc::UTIME_NOW;
2980 } else if valid.contains(SetattrValid::ATIME) {
2981 tvs[0].tv_sec = attr.st_atime;
2982 tvs[0].tv_nsec = attr.st_atime_nsec;
2983 }
2984
2985 if valid.contains(SetattrValid::MTIME_NOW) {
2986 tvs[1].tv_nsec = libc::UTIME_NOW;
2987 } else if valid.contains(SetattrValid::MTIME) {
2988 tvs[1].tv_sec = attr.st_mtime;
2989 tvs[1].tv_nsec = attr.st_mtime_nsec;
2990 }
2991
2992 syscall!(unsafe {
2994 match data {
2995 Data::Handle(ref fd) => libc::futimens(fd.as_raw_descriptor(), tvs.as_ptr()),
2996 Data::ProcPath(ref p) => {
2997 libc::utimensat(self.proc.as_raw_descriptor(), p.as_ptr(), tvs.as_ptr(), 0)
2998 }
2999 }
3000 })?;
3001 }
3002
3003 self.do_getattr(&inode_data)
3004 }
3005
3006 fn rename(
3007 &self,
3008 _ctx: Context,
3009 olddir: Inode,
3010 oldname: &CStr,
3011 newdir: Inode,
3012 newname: &CStr,
3013 flags: u32,
3014 ) -> io::Result<()> {
3015 let _trace = fs_trace!(self.tag, "rename", olddir, oldname, newdir, newname, flags);
3016 let old_inode = self.find_inode(olddir)?;
3017 let new_inode = self.find_inode(newdir)?;
3018 self.authorize_write_path(&old_inode.path, oldname)?;
3020 self.authorize_write_path(&new_inode.path, newname)?;
3021 {
3022 let casefold_cache = self.lock_casefold_lookup_caches();
3023
3024 syscall!(unsafe {
3028 libc::syscall(
3029 libc::SYS_renameat2,
3030 old_inode.as_raw_descriptor(),
3031 oldname.as_ptr(),
3032 new_inode.as_raw_descriptor(),
3033 newname.as_ptr(),
3034 flags,
3035 )
3036 })?;
3037 if let Some(mut c) = casefold_cache {
3038 c.remove(olddir, oldname);
3039 c.insert(newdir, newname);
3040 }
3041 }
3042
3043 Ok(())
3044 }
3045
3046 fn mknod(
3047 &self,
3048 ctx: Context,
3049 parent: Inode,
3050 name: &CStr,
3051 mode: u32,
3052 rdev: u32,
3053 umask: u32,
3054 security_ctx: Option<&CStr>,
3055 ) -> io::Result<Entry> {
3056 let _trace = fs_trace!(
3057 self.tag,
3058 "mknod",
3059 parent,
3060 name,
3061 mode,
3062 rdev,
3063 umask,
3064 security_ctx
3065 );
3066 let data = self.find_inode(parent)?;
3067 self.authorize_write_path(&data.path, name)?;
3068
3069 let _ctx = security_ctx
3070 .filter(|ctx| *ctx != UNLABELED_CSTR)
3071 .map(|ctx| ScopedSecurityContext::new(&self.proc, ctx))
3072 .transpose()?;
3073
3074 #[allow(unused_variables)]
3075 #[cfg(feature = "arc_quota")]
3076 let (uid, gid) = self.change_creds(&ctx, &data, name);
3077 #[cfg(feature = "fs_runtime_ugid_map")]
3078 let (uid, gid) = self.change_ugid_creds(&ctx, &data, name);
3079 #[cfg(not(feature = "fs_permission_translation"))]
3080 let (uid, gid) = (ctx.uid, ctx.gid);
3081
3082 let (_uid, _gid) = set_creds(uid, gid)?;
3083 {
3084 let _scoped_umask = ScopedUmask::new(umask);
3085 let casefold_cache = self.lock_casefold_lookup_caches();
3086
3087 syscall!(unsafe {
3089 libc::mknodat(
3090 data.as_raw_descriptor(),
3091 name.as_ptr(),
3092 mode as libc::mode_t,
3093 rdev as libc::dev_t,
3094 )
3095 })?;
3096 if let Some(mut c) = casefold_cache {
3097 c.insert(parent, name);
3098 }
3099 }
3100
3101 self.do_lookup(&data, name)
3102 }
3103
3104 fn link(
3105 &self,
3106 _ctx: Context,
3107 inode: Inode,
3108 newparent: Inode,
3109 newname: &CStr,
3110 ) -> io::Result<Entry> {
3111 let _trace = fs_trace!(self.tag, "link", inode, newparent, newname);
3112 let data = self.find_inode(inode)?;
3113 let new_inode = self.find_inode(newparent)?;
3114 self.authorize_write_path(&new_inode.path, newname)?;
3115
3116 let path = CString::new(format!("self/fd/{}", data.as_raw_descriptor()))
3117 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
3118
3119 {
3120 let casefold_cache = self.lock_casefold_lookup_caches();
3121 syscall!(unsafe {
3123 libc::linkat(
3124 self.proc.as_raw_descriptor(),
3125 path.as_ptr(),
3126 new_inode.as_raw_descriptor(),
3127 newname.as_ptr(),
3128 libc::AT_SYMLINK_FOLLOW,
3129 )
3130 })?;
3131 if let Some(mut c) = casefold_cache {
3132 c.insert(newparent, newname);
3133 }
3134 }
3135
3136 self.do_lookup(&new_inode, newname)
3137 }
3138
3139 fn symlink(
3140 &self,
3141 ctx: Context,
3142 linkname: &CStr,
3143 parent: Inode,
3144 name: &CStr,
3145 security_ctx: Option<&CStr>,
3146 ) -> io::Result<Entry> {
3147 let _trace = fs_trace!(self.tag, "symlink", parent, linkname, name, security_ctx);
3148 let data = self.find_inode(parent)?;
3149 self.authorize_write_path(&data.path, name)?;
3150
3151 let _ctx = security_ctx
3152 .filter(|ctx| *ctx != UNLABELED_CSTR)
3153 .map(|ctx| ScopedSecurityContext::new(&self.proc, ctx))
3154 .transpose()?;
3155
3156 #[allow(unused_variables)]
3157 #[cfg(feature = "arc_quota")]
3158 let (uid, gid) = self.change_creds(&ctx, &data, name);
3159 #[cfg(feature = "fs_runtime_ugid_map")]
3160 let (uid, gid) = self.change_ugid_creds(&ctx, &data, name);
3161 #[cfg(not(feature = "fs_permission_translation"))]
3162 let (uid, gid) = (ctx.uid, ctx.gid);
3163
3164 let (_uid, _gid) = set_creds(uid, gid)?;
3165 {
3166 let casefold_cache = self.lock_casefold_lookup_caches();
3167 syscall!(unsafe {
3169 libc::symlinkat(linkname.as_ptr(), data.as_raw_descriptor(), name.as_ptr())
3170 })?;
3171 if let Some(mut c) = casefold_cache {
3172 c.insert(parent, name);
3173 }
3174 }
3175
3176 self.do_lookup(&data, name)
3177 }
3178
3179 fn readlink(&self, _ctx: Context, inode: Inode) -> io::Result<Vec<u8>> {
3180 let _trace = fs_trace!(self.tag, "readlink", inode);
3181 let data = self.find_inode(inode)?;
3182
3183 let mut buf = vec![0; libc::PATH_MAX as usize];
3184
3185 let res = syscall!(unsafe {
3187 libc::readlinkat(
3188 data.as_raw_descriptor(),
3189 EMPTY_CSTR.as_ptr(),
3190 buf.as_mut_ptr() as *mut libc::c_char,
3191 buf.len(),
3192 )
3193 })?;
3194
3195 buf.resize(res as usize, 0);
3196
3197 #[cfg(feature = "fs_runtime_ugid_map")]
3198 {
3199 let link_target = Path::new(OsStr::from_bytes(&buf[..res as usize]));
3200 if !link_target.starts_with(&self.root_dir) {
3201 return Err(io::Error::new(
3202 io::ErrorKind::InvalidInput,
3203 "Symbolic link points outside of root_dir",
3204 ));
3205 }
3206 }
3207 Ok(buf)
3208 }
3209
3210 fn flush(
3211 &self,
3212 _ctx: Context,
3213 inode: Inode,
3214 handle: Handle,
3215 _lock_owner: u64,
3216 ) -> io::Result<()> {
3217 let _trace = fs_trace!(self.tag, "flush", inode, handle);
3218 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
3219 self.find_inode(inode)?
3220 } else {
3221 self.find_handle(handle, inode)?
3222 };
3223
3224 unsafe {
3229 let newfd = syscall!(libc::fcntl(
3230 data.as_raw_descriptor(),
3231 libc::F_DUPFD_CLOEXEC,
3232 0
3233 ))?;
3234
3235 syscall!(libc::close(newfd))?;
3236 }
3237 Ok(())
3238 }
3239
3240 fn fsync(&self, _ctx: Context, inode: Inode, datasync: bool, handle: Handle) -> io::Result<()> {
3241 if self.zero_message_open.load(Ordering::Relaxed) {
3242 let _trace = fs_trace!(self.tag, "fsync (zero-message)", inode, datasync, handle);
3243 let data = self.find_inode(inode)?;
3244 self.do_fsync(&*data, datasync)
3245 } else {
3246 let _trace = fs_trace!(self.tag, "fsync", inode, datasync, handle);
3247 let data = self.find_handle(handle, inode)?;
3248
3249 let file = data.file.lock();
3250 self.do_fsync(&*file, datasync)
3251 }
3252 }
3253
3254 fn fsyncdir(
3255 &self,
3256 _ctx: Context,
3257 inode: Inode,
3258 datasync: bool,
3259 handle: Handle,
3260 ) -> io::Result<()> {
3261 if self.zero_message_opendir.load(Ordering::Relaxed) {
3262 let _trace = fs_trace!(self.tag, "fsyncdir (zero-message)", inode, datasync, handle);
3263 let data = self.find_inode(inode)?;
3264 self.do_fsync(&*data, datasync)
3265 } else {
3266 let _trace = fs_trace!(self.tag, "fsyncdir", inode, datasync, handle);
3267 let data = self.find_handle(handle, inode)?;
3268
3269 let file = data.file.lock();
3270 self.do_fsync(&*file, datasync)
3271 }
3272 }
3273
3274 fn access(&self, ctx: Context, inode: Inode, mask: u32) -> io::Result<()> {
3275 let _trace = fs_trace!(self.tag, "access", inode, mask);
3276 let data = self.find_inode(inode)?;
3277
3278 let st = stat(&*data)?;
3279 let mode = mask as i32 & (libc::R_OK | libc::W_OK | libc::X_OK);
3280
3281 if mode == libc::F_OK {
3282 return Ok(());
3284 }
3285
3286 if (mode & libc::R_OK) != 0 {
3287 if ctx.uid != 0
3288 && (st.st_uid != ctx.uid || st.st_mode & 0o400 == 0)
3289 && (st.st_gid != ctx.gid || st.st_mode & 0o040 == 0)
3290 && st.st_mode & 0o004 == 0
3291 {
3292 return Err(io::Error::from_raw_os_error(libc::EACCES));
3293 }
3294 }
3295
3296 if (mode & libc::W_OK) != 0 {
3297 if ctx.uid != 0
3298 && (st.st_uid != ctx.uid || st.st_mode & 0o200 == 0)
3299 && (st.st_gid != ctx.gid || st.st_mode & 0o020 == 0)
3300 && st.st_mode & 0o002 == 0
3301 {
3302 return Err(io::Error::from_raw_os_error(libc::EACCES));
3303 }
3304 }
3305
3306 if (mode & libc::X_OK) != 0 {
3309 if (ctx.uid != 0 || st.st_mode & 0o111 == 0)
3310 && (st.st_uid != ctx.uid || st.st_mode & 0o100 == 0)
3311 && (st.st_gid != ctx.gid || st.st_mode & 0o010 == 0)
3312 && st.st_mode & 0o001 == 0
3313 {
3314 return Err(io::Error::from_raw_os_error(libc::EACCES));
3315 }
3316 }
3317
3318 Ok(())
3319 }
3320
3321 fn setxattr(
3322 &self,
3323 _ctx: Context,
3324 inode: Inode,
3325 name: &CStr,
3326 value: &[u8],
3327 flags: u32,
3328 ) -> io::Result<()> {
3329 let _trace = fs_trace!(self.tag, "setxattr", inode, name, flags);
3330 if self.cfg.rewrite_security_xattrs && name.to_bytes().starts_with(USER_VIRTIOFS_XATTR) {
3333 return Err(io::Error::from_raw_os_error(libc::EPERM));
3334 }
3335
3336 let data = self.find_inode(inode)?;
3337 let name = self.rewrite_xattr_name(name);
3338
3339 #[cfg(feature = "arc_quota")]
3340 if self.skip_host_set_xattr(&data.path, &name.to_string_lossy()) {
3341 debug!(
3342 "ignore setxattr for path:{} xattr_name:{}",
3343 &data.path,
3344 &name.to_string_lossy()
3345 );
3346 return Ok(());
3347 }
3348
3349 let file = data.file.lock();
3350 let o_path_file = (file.open_flags & libc::O_PATH) != 0;
3351 if o_path_file {
3352 let path = CString::new(format!("self/fd/{}", file.as_raw_descriptor()))
3356 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
3357
3358 syscall!(self.with_proc_chdir(|| {
3359 unsafe {
3361 libc::setxattr(
3362 path.as_ptr(),
3363 name.as_ptr(),
3364 value.as_ptr() as *const libc::c_void,
3365 value.len() as libc::size_t,
3366 flags as c_int,
3367 )
3368 }
3369 }))?;
3370 } else {
3371 syscall!(
3372 unsafe {
3375 libc::fsetxattr(
3376 file.as_raw_descriptor(),
3377 name.as_ptr(),
3378 value.as_ptr() as *const libc::c_void,
3379 value.len() as libc::size_t,
3380 flags as c_int,
3381 )
3382 }
3383 )?;
3384 }
3385
3386 Ok(())
3387 }
3388
3389 fn getxattr(
3390 &self,
3391 _ctx: Context,
3392 inode: Inode,
3393 name: &CStr,
3394 size: u32,
3395 ) -> io::Result<GetxattrReply> {
3396 let _trace = fs_trace!(self.tag, "getxattr", inode, name, size);
3397 if self.cfg.rewrite_security_xattrs && name.to_bytes().starts_with(USER_VIRTIOFS_XATTR) {
3400 return Err(io::Error::from_raw_os_error(libc::ENODATA));
3401 }
3402
3403 let data = self.find_inode(inode)?;
3404 let name = self.rewrite_xattr_name(name);
3405 let mut buf = vec![0u8; size as usize];
3406
3407 #[cfg(feature = "arc_quota")]
3408 let res = self.do_getxattr_with_filter(data, name, &mut buf)?;
3409
3410 #[cfg(not(feature = "arc_quota"))]
3411 let res = self.do_getxattr(&data, &name, &mut buf[..])?;
3412
3413 if size == 0 {
3414 Ok(GetxattrReply::Count(res as u32))
3415 } else {
3416 buf.truncate(res);
3417 Ok(GetxattrReply::Value(buf))
3418 }
3419 }
3420
3421 fn listxattr(&self, _ctx: Context, inode: Inode, size: u32) -> io::Result<ListxattrReply> {
3422 let _trace = fs_trace!(self.tag, "listxattr", inode, size);
3423 let data = self.find_inode(inode)?;
3424
3425 let mut buf = vec![0u8; size as usize];
3426
3427 let file = data.file.lock();
3428 let o_path_file = (file.open_flags & libc::O_PATH) != 0;
3429 let res = if o_path_file {
3430 let path = CString::new(format!("self/fd/{}", file.as_raw_descriptor()))
3434 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
3435
3436 syscall!(self.with_proc_chdir(|| unsafe {
3438 libc::listxattr(
3439 path.as_ptr(),
3440 buf.as_mut_ptr() as *mut libc::c_char,
3441 buf.len() as libc::size_t,
3442 )
3443 }))?
3444 } else {
3445 syscall!(unsafe {
3448 libc::flistxattr(
3449 file.as_raw_descriptor(),
3450 buf.as_mut_ptr() as *mut libc::c_char,
3451 buf.len() as libc::size_t,
3452 )
3453 })?
3454 };
3455
3456 if size == 0 {
3457 Ok(ListxattrReply::Count(res as u32))
3458 } else {
3459 buf.truncate(res as usize);
3460
3461 if self.cfg.rewrite_security_xattrs {
3462 strip_xattr_prefix(&mut buf);
3463 }
3464 Ok(ListxattrReply::Names(buf))
3465 }
3466 }
3467
3468 fn removexattr(&self, _ctx: Context, inode: Inode, name: &CStr) -> io::Result<()> {
3469 let _trace = fs_trace!(self.tag, "removexattr", inode, name);
3470 if self.cfg.rewrite_security_xattrs && name.to_bytes().starts_with(USER_VIRTIOFS_XATTR) {
3473 return Err(io::Error::from_raw_os_error(libc::ENODATA));
3474 }
3475
3476 let data = self.find_inode(inode)?;
3477 let name = self.rewrite_xattr_name(name);
3478
3479 let file = data.file.lock();
3480 let o_path_file = (file.open_flags & libc::O_PATH) != 0;
3481 if o_path_file {
3482 let path = CString::new(format!("self/fd/{}", file.as_raw_descriptor()))
3486 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
3487
3488 syscall!(self.with_proc_chdir(||
3489 unsafe { libc::removexattr(path.as_ptr(), name.as_ptr()) }))?;
3491 } else {
3492 syscall!(
3494 unsafe { libc::fremovexattr(file.as_raw_descriptor(), name.as_ptr()) }
3496 )?;
3497 }
3498
3499 Ok(())
3500 }
3501
3502 fn fallocate(
3503 &self,
3504 _ctx: Context,
3505 inode: Inode,
3506 handle: Handle,
3507 mode: u32,
3508 offset: u64,
3509 length: u64,
3510 ) -> io::Result<()> {
3511 let _trace = fs_trace!(self.tag, "fallocate", inode, handle, mode, offset, length);
3512
3513 let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
3514 let data = self.find_inode(inode)?;
3515
3516 {
3517 let mut file = data.file.lock();
3519 let mut flags = file.open_flags;
3520 match flags & libc::O_ACCMODE {
3521 libc::O_RDONLY => {
3522 flags &= !libc::O_RDONLY;
3523 flags |= libc::O_RDWR;
3524
3525 let newfile = self.open_fd(file.as_raw_descriptor(), libc::O_RDWR)?;
3527 *file = OpenedFile::new(newfile, flags);
3528 }
3529 libc::O_WRONLY | libc::O_RDWR => {}
3530 _ => panic!("Unexpected flags: {flags:#x}"),
3531 }
3532 }
3533
3534 data
3535 } else {
3536 self.find_handle(handle, inode)?
3537 };
3538
3539 let fd = data.as_raw_descriptor();
3540 syscall!(unsafe {
3542 libc::fallocate64(
3543 fd,
3544 mode as libc::c_int,
3545 offset as libc::off64_t,
3546 length as libc::off64_t,
3547 )
3548 })?;
3549
3550 Ok(())
3551 }
3552
3553 #[allow(clippy::unnecessary_cast)]
3554 fn ioctl<R: io::Read>(
3555 &self,
3556 ctx: Context,
3557 inode: Inode,
3558 handle: Handle,
3559 _flags: IoctlFlags,
3560 cmd: u32,
3561 _arg: u64,
3562 in_size: u32,
3563 out_size: u32,
3564 r: R,
3565 ) -> io::Result<IoctlReply> {
3566 let _trace = fs_trace!(self.tag, "ioctl", inode, handle, cmd, in_size, out_size);
3567
3568 match cmd as IoctlNr {
3569 FS_IOC_GET_ENCRYPTION_POLICY_EX => self.get_encryption_policy_ex(inode, handle, r),
3570 FS_IOC_FSGETXATTR => {
3571 if out_size < size_of::<fsxattr>() as u32 {
3572 Err(io::Error::from_raw_os_error(libc::ENOMEM))
3573 } else {
3574 self.get_fsxattr(inode, handle)
3575 }
3576 }
3577 FS_IOC_FSSETXATTR => {
3578 if in_size < size_of::<fsxattr>() as u32 {
3579 Err(io::Error::from_raw_os_error(libc::EINVAL))
3580 } else {
3581 self.set_fsxattr(ctx, inode, handle, r)
3582 }
3583 }
3584 FS_IOC32_GETFLAGS | FS_IOC64_GETFLAGS => {
3585 if out_size < size_of::<c_int>() as u32 {
3586 Err(io::Error::from_raw_os_error(libc::ENOMEM))
3587 } else {
3588 self.get_flags(inode, handle)
3589 }
3590 }
3591 FS_IOC32_SETFLAGS | FS_IOC64_SETFLAGS => {
3592 if in_size < size_of::<c_int>() as u32 {
3593 Err(io::Error::from_raw_os_error(libc::ENOMEM))
3594 } else {
3595 self.set_flags(ctx, inode, handle, r)
3596 }
3597 }
3598 FS_IOC_ENABLE_VERITY => {
3599 if in_size < size_of::<fsverity_enable_arg>() as u32 {
3600 Err(io::Error::from_raw_os_error(libc::ENOMEM))
3601 } else {
3602 self.enable_verity(inode, handle, r)
3603 }
3604 }
3605 FS_IOC_MEASURE_VERITY => {
3606 if in_size < size_of::<fsverity_digest>() as u32
3607 || out_size < size_of::<fsverity_digest>() as u32
3608 {
3609 Err(io::Error::from_raw_os_error(libc::ENOMEM))
3610 } else {
3611 self.measure_verity(inode, handle, r, out_size)
3612 }
3613 }
3614 #[cfg(feature = "arc_quota")]
3617 FS_IOC_SETPERMISSION => {
3618 if in_size != size_of::<FsPermissionDataBuffer>() as u32 {
3619 Err(io::Error::from_raw_os_error(libc::EINVAL))
3620 } else {
3621 Ok(self.set_permission_by_path(r))
3622 }
3623 }
3624 #[cfg(feature = "arc_quota")]
3625 FS_IOC_SETPATHXATTR => {
3626 if in_size != size_of::<FsPathXattrDataBuffer>() as u32 {
3627 Err(io::Error::from_raw_os_error(libc::EINVAL))
3628 } else {
3629 Ok(self.set_xattr_by_path(r))
3630 }
3631 }
3632 _ => Err(io::Error::from_raw_os_error(libc::ENOTTY)),
3633 }
3634 }
3635
3636 fn copy_file_range(
3637 &self,
3638 ctx: Context,
3639 inode_src: Inode,
3640 handle_src: Handle,
3641 offset_src: u64,
3642 inode_dst: Inode,
3643 handle_dst: Handle,
3644 offset_dst: u64,
3645 length: u64,
3646 flags: u64,
3647 ) -> io::Result<usize> {
3648 let _trace = fs_trace!(
3649 self.tag,
3650 "copy_file_range",
3651 inode_src,
3652 handle_src,
3653 offset_src,
3654 inode_dst,
3655 handle_dst,
3656 offset_dst,
3657 length,
3658 flags
3659 );
3660 let dst_inode_data = self.find_inode(inode_dst)?;
3661
3662 #[allow(unused_variables)]
3663 #[cfg(feature = "arc_quota")]
3664 let (uid, gid) = self.change_creds_for_path(&ctx, &dst_inode_data.path);
3665 #[cfg(feature = "fs_runtime_ugid_map")]
3666 let (uid, gid) = self.change_ugid_creds_for_path(&ctx, &dst_inode_data.path);
3667 #[cfg(not(feature = "fs_permission_translation"))]
3668 let (uid, gid) = (ctx.uid, ctx.gid);
3669
3670 let (_uid, _gid) = set_creds(uid, gid)?;
3673 let (src_data, dst_data): (Arc<dyn AsRawDescriptor>, Arc<dyn AsRawDescriptor>) =
3674 if self.zero_message_open.load(Ordering::Relaxed) {
3675 (self.find_inode(inode_src)?, dst_inode_data)
3676 } else {
3677 (
3678 self.find_handle(handle_src, inode_src)?,
3679 self.find_handle(handle_dst, inode_dst)?,
3680 )
3681 };
3682
3683 let src = src_data.as_raw_descriptor();
3684 let dst = dst_data.as_raw_descriptor();
3685
3686 Ok(syscall!(
3687 unsafe {
3690 libc::syscall(
3691 libc::SYS_copy_file_range,
3692 src,
3693 &offset_src,
3694 dst,
3695 &offset_dst,
3696 length,
3697 flags,
3698 )
3699 }
3700 )? as usize)
3701 }
3702
3703 fn set_up_mapping<M: Mapper>(
3704 &self,
3705 _ctx: Context,
3706 inode: Self::Inode,
3707 _handle: Self::Handle,
3708 file_offset: u64,
3709 mem_offset: u64,
3710 size: usize,
3711 prot: u32,
3712 mapper: M,
3713 ) -> io::Result<()> {
3714 let _trace = fs_trace!(
3715 self.tag,
3716 "set_up_mapping",
3717 inode,
3718 file_offset,
3719 mem_offset,
3720 size,
3721 prot
3722 );
3723 if !self.cfg.use_dax {
3724 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
3725 }
3726
3727 let read = prot & libc::PROT_READ as u32 != 0;
3728 let write = prot & libc::PROT_WRITE as u32 != 0;
3729 let (mmap_flags, prot) = match (read, write) {
3730 (true, true) => (libc::O_RDWR, Protection::read_write()),
3731 (true, false) => (libc::O_RDONLY, Protection::read()),
3732 (false, true) => (libc::O_RDWR, Protection::write()),
3734 (false, false) => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
3735 };
3736
3737 let data = self.find_inode(inode)?;
3738
3739 if self.zero_message_open.load(Ordering::Relaxed) {
3740 let mut file = data.file.lock();
3741 let mut open_flags = file.open_flags;
3742 match (mmap_flags, open_flags & libc::O_ACCMODE) {
3743 (libc::O_RDONLY, libc::O_WRONLY)
3744 | (libc::O_RDWR, libc::O_RDONLY)
3745 | (libc::O_RDWR, libc::O_WRONLY) => {
3746 open_flags &= !libc::O_ACCMODE;
3748 open_flags |= libc::O_RDWR;
3749
3750 let newfile = self.open_fd(file.as_raw_descriptor(), libc::O_RDWR)?;
3751 *file = OpenedFile::new(newfile, open_flags);
3752 }
3753 (libc::O_RDONLY, libc::O_RDONLY)
3754 | (libc::O_RDONLY, libc::O_RDWR)
3755 | (libc::O_RDWR, libc::O_RDWR) => {}
3756 (m, o) => panic!("Unexpected combination of access flags: ({m:#x}, {o:#x})"),
3757 }
3758 mapper.map(mem_offset, size, file.file(), file_offset, prot)
3759 } else {
3760 let file = self.open_inode(&data, mmap_flags | libc::O_NONBLOCK)?;
3761 mapper.map(mem_offset, size, &file, file_offset, prot)
3762 }
3763 }
3764
3765 fn remove_mapping<M: Mapper>(&self, msgs: &[RemoveMappingOne], mapper: M) -> io::Result<()> {
3766 let _trace = fs_trace!(self.tag, "remove_mapping", msgs);
3767 if !self.cfg.use_dax {
3768 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
3769 }
3770
3771 for RemoveMappingOne { moffset, len } in msgs {
3772 mapper.unmap(*moffset, *len)?;
3773 }
3774 Ok(())
3775 }
3776
3777 fn atomic_open(
3778 &self,
3779 ctx: Context,
3780 parent: Self::Inode,
3781 name: &CStr,
3782 mode: u32,
3783 flags: u32,
3784 umask: u32,
3785 security_ctx: Option<&CStr>,
3786 ) -> io::Result<(Entry, Option<Self::Handle>, OpenOptions)> {
3787 validate_path_component(name)?;
3788 let _trace = fs_trace!(
3789 self.tag,
3790 "atomic_open",
3791 parent,
3792 name,
3793 mode,
3794 flags,
3795 umask,
3796 security_ctx
3797 );
3798 let data = self.find_inode(parent)?;
3800
3801 #[allow(unused_variables)]
3802 #[cfg(feature = "arc_quota")]
3803 let (uid, gid) = self.change_creds(&ctx, &data, name);
3804 #[cfg(feature = "fs_runtime_ugid_map")]
3805 let (uid, gid) = self.change_ugid_creds(&ctx, &data, name);
3806 #[cfg(not(feature = "fs_permission_translation"))]
3807 let (uid, gid) = (ctx.uid, ctx.gid);
3808
3809 let (_uid, _gid) = set_creds(uid, gid)?;
3810
3811 let res = self.do_lookup_with_casefold_fallback(&data, name);
3815
3816 if let Err(e) = res {
3817 if e.kind() == std::io::ErrorKind::NotFound && (flags as i32 & libc::O_CREAT) != 0 {
3818 let (entry, handler, mut opts) =
3821 self.create(ctx, parent, name, mode, flags, umask, security_ctx)?;
3822 opts |= OpenOptions::FILE_CREATED;
3823 return Ok((entry, handler, opts));
3824 } else if e.kind() == std::io::ErrorKind::NotFound
3825 && !self.cfg.negative_timeout.is_zero()
3826 {
3827 return Ok((
3828 Entry::new_negative(self.cfg.negative_timeout),
3829 None,
3830 OpenOptions::empty(),
3831 ));
3832 }
3833 return Err(e);
3834 }
3835
3836 let entry = res.unwrap();
3838
3839 if entry.attr.st_mode & libc::S_IFMT == libc::S_IFLNK {
3840 return Ok((entry, None, OpenOptions::empty()));
3841 }
3842
3843 if (flags as i32 & (libc::O_CREAT | libc::O_EXCL)) == (libc::O_CREAT | libc::O_EXCL) {
3844 return Err(eexist());
3845 }
3846
3847 let (handler, opts) = if self.zero_message_open.load(Ordering::Relaxed) {
3848 (None, OpenOptions::KEEP_CACHE)
3849 } else {
3850 let (handler, opts) = self.do_open(entry.inode, flags)?;
3851 (handler, opts)
3852 };
3853 Ok((entry, handler, opts))
3854 }
3855}
3856
3857#[cfg(test)]
3858mod tests {
3859 use std::path::Path;
3860
3861 use named_lock::NamedLock;
3862 use tempfile::TempDir;
3863
3864 use super::*;
3865 #[cfg(feature = "arc_quota")]
3866 use crate::virtio::fs::arc_ioctl::FS_IOCTL_PATH_MAX_LEN;
3867 #[cfg(feature = "arc_quota")]
3868 use crate::virtio::fs::arc_ioctl::FS_IOCTL_XATTR_NAME_MAX_LEN;
3869 #[cfg(feature = "arc_quota")]
3870 use crate::virtio::fs::arc_ioctl::FS_IOCTL_XATTR_VALUE_MAX_LEN;
3871
3872 const UNITTEST_LOCK_NAME: &str = "passthroughfs_unittest_lock";
3873
3874 #[test]
3875 fn test_passthrough_fs_allowlist() {
3876 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
3877 let _guard = lock.lock().expect("acquire named lock");
3878
3879 let temp_dir = TempDir::new().unwrap();
3880 create_test_data(
3881 &temp_dir,
3882 &["allowed", "blocked"],
3883 &["allowed/a.txt", "blocked/b.txt"],
3884 );
3885
3886 let cfg = Default::default();
3887 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
3888
3889 let capable = FsOptions::empty();
3890 fs.init(capable).unwrap();
3891
3892 let allowlist = Arc::new(RwLock::new(PathAllowlist::new()));
3893 fs.set_allowlist(Some(allowlist.clone()));
3894
3895 let allowed_path = temp_dir
3896 .path()
3897 .join("allowed")
3898 .to_string_lossy()
3899 .into_owned();
3900 allowlist.write().unwrap().add_path(allowed_path);
3901
3902 assert!(lookup(&fs, &temp_dir.path().join("allowed")).is_ok());
3904 assert!(lookup(&fs, &temp_dir.path().join("allowed/a.txt")).is_ok());
3905
3906 let blocked_err = lookup(&fs, &temp_dir.path().join("blocked"))
3908 .expect_err("blocked directory must not be accessible");
3909 assert_eq!(blocked_err.kind(), io::ErrorKind::NotFound);
3910
3911 let blocked_file_err = lookup(&fs, &temp_dir.path().join("blocked/b.txt"))
3912 .expect_err("blocked file must not be accessible");
3913 assert_eq!(blocked_file_err.kind(), io::ErrorKind::NotFound);
3914
3915 assert!(create(&fs, &temp_dir.path().join("allowed/new_file.txt")).is_ok());
3917
3918 let blocked_dir_write_err = create(&fs, &temp_dir.path().join("blocked/new_file.txt"))
3921 .expect_err("parent directory must not be lookupable");
3922 assert_eq!(blocked_dir_write_err.kind(), io::ErrorKind::NotFound);
3923
3924 let ancestor_write_err = create(&fs, &temp_dir.path().join("new_file_in_ancestor.txt"))
3929 .expect_err("ancestor directory must not be writable");
3930 assert_eq!(ancestor_write_err.kind(), io::ErrorKind::PermissionDenied);
3931 }
3932
3933 #[test]
3934 fn test_passthrough_fs_revocation() {
3935 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
3936 let _guard = lock.lock().expect("acquire named lock");
3937
3938 let temp_dir = TempDir::new().unwrap();
3939 create_test_data(&temp_dir, &["allowed"], &["allowed/a.txt"]);
3940
3941 let file_path = temp_dir.path().join("allowed/a.txt");
3943 std::fs::write(&file_path, b"hello revocation").unwrap();
3944
3945 let cfg = Default::default();
3946 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
3947
3948 let capable = FsOptions::empty();
3949 fs.init(capable).unwrap();
3950
3951 let allowlist = Arc::new(RwLock::new(PathAllowlist::new()));
3952 fs.set_allowlist(Some(allowlist.clone()));
3953
3954 let allowed_path = temp_dir
3955 .path()
3956 .join("allowed")
3957 .to_string_lossy()
3958 .into_owned();
3959 allowlist.write().unwrap().add_path(allowed_path.clone());
3960
3961 let inode = lookup(&fs, &file_path).expect("lookup failed");
3963 let ctx = get_context();
3964 let (handle, _) = fs
3965 .open(ctx, inode, libc::O_RDONLY as u32)
3966 .expect("open failed");
3967 let handle = handle.expect("no handle returned");
3968
3969 allowlist.write().unwrap().remove_path(&allowed_path);
3971
3972 struct DummyWriter {
3974 data: Vec<u8>,
3975 }
3976 impl io::Write for DummyWriter {
3977 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3978 self.data.extend_from_slice(buf);
3979 Ok(buf.len())
3980 }
3981 fn flush(&mut self) -> io::Result<()> {
3982 Ok(())
3983 }
3984 }
3985 impl ZeroCopyWriter for DummyWriter {
3986 fn write_from(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
3987 use std::os::unix::fs::FileExt;
3988 let mut buf = vec![0; count];
3989 let n = f.read_at(&mut buf, off)?;
3990 self.data.extend_from_slice(&buf[..n]);
3991 Ok(n)
3992 }
3993 }
3994
3995 let mut writer = DummyWriter { data: Vec::new() };
3996 let read_bytes = fs
3997 .read(
3998 ctx,
3999 inode,
4000 handle,
4001 &mut writer,
4002 16,
4003 0,
4004 None,
4005 libc::O_RDONLY as u32,
4006 )
4007 .expect("read failed");
4008
4009 assert_eq!(read_bytes, 16);
4010 assert_eq!(writer.data, b"hello revocation");
4011
4012 let lookup_res = lookup(&fs, &file_path);
4014 assert!(lookup_res.is_err());
4015 assert_eq!(lookup_res.unwrap_err().kind(), io::ErrorKind::NotFound);
4016 }
4017
4018 fn get_context() -> Context {
4021 let uid = unsafe { libc::syscall(SYS_GETEUID) as libc::uid_t };
4024 let gid = unsafe { libc::syscall(SYS_GETEGID) as libc::gid_t };
4027 let pid = std::process::id() as libc::pid_t;
4028 Context { uid, gid, pid }
4029 }
4030
4031 fn create_test_data(temp_dir: &TempDir, dirs: &[&str], files: &[&str]) {
4033 let path = temp_dir.path();
4034
4035 for d in dirs {
4036 std::fs::create_dir_all(path.join(d)).unwrap();
4037 }
4038
4039 for f in files {
4040 File::create(path.join(f)).unwrap();
4041 }
4042 }
4043
4044 fn lookup(fs: &PassthroughFs, path: &Path) -> io::Result<Inode> {
4046 let mut inode = 1;
4047 let ctx = get_context();
4048 for name in path.iter() {
4049 let name = CString::new(name.to_str().unwrap()).unwrap();
4050 let ent = match fs.lookup(ctx, inode, &name) {
4051 Ok(ent) => ent,
4052 Err(e) => {
4053 return Err(e);
4054 }
4055 };
4056 inode = ent.inode;
4057 }
4058 Ok(inode)
4059 }
4060
4061 #[cfg(feature = "arc_quota")]
4063 fn lookup_ent(fs: &PassthroughFs, path: &Path) -> io::Result<Entry> {
4064 let mut inode = 1;
4065 let ctx = get_context();
4066 let mut entry = Entry::new_negative(Duration::from_secs(10));
4067 for name in path.iter() {
4068 let name = CString::new(name.to_str().unwrap()).unwrap();
4069 entry = match fs.lookup(ctx, inode, &name) {
4070 Ok(ent) => ent,
4071 Err(e) => {
4072 return Err(e);
4073 }
4074 };
4075 inode = entry.inode;
4076 }
4077 Ok(entry)
4078 }
4079
4080 fn create(fs: &PassthroughFs, path: &Path) -> io::Result<Entry> {
4082 let parent = path.parent().unwrap();
4083 let filename = CString::new(path.file_name().unwrap().to_str().unwrap()).unwrap();
4084 let parent_inode = lookup(fs, parent)?;
4085 let ctx = get_context();
4086 let security_ctx = None;
4087 fs.create(
4088 ctx,
4089 parent_inode,
4090 &filename,
4091 0o666,
4092 libc::O_RDWR as u32,
4093 0,
4094 security_ctx,
4095 )
4096 .map(|(entry, _, _)| entry)
4097 }
4098
4099 fn unlink(fs: &PassthroughFs, path: &Path) -> io::Result<()> {
4101 let parent = path.parent().unwrap();
4102 let filename = CString::new(path.file_name().unwrap().to_str().unwrap()).unwrap();
4103 let parent_inode = lookup(fs, parent)?;
4104 let ctx = get_context();
4105 fs.unlink(ctx, parent_inode, &filename)
4106 }
4107
4108 fn forget(fs: &PassthroughFs, path: &Path) -> io::Result<()> {
4110 let ctx = get_context();
4111 let inode = lookup(fs, path)?;
4112 fs.forget(ctx, inode, u64::MAX);
4114 Ok(())
4115 }
4116
4117 fn atomic_open(
4119 fs: &PassthroughFs,
4120 path: &Path,
4121 mode: u32,
4122 flags: u32,
4123 umask: u32,
4124 security_ctx: Option<&CStr>,
4125 ) -> io::Result<(Entry, Option<Handle>, OpenOptions)> {
4126 let mut inode = 1;
4127 let ctx = get_context();
4128
4129 let path_vec: Vec<_> = path.iter().collect();
4130 let vec_len = path_vec.len();
4131
4132 for name in &path_vec[0..vec_len - 1] {
4135 let name = CString::new(name.to_str().unwrap()).unwrap();
4136 let ent = fs.lookup(ctx, inode, &name)?;
4137 inode = ent.inode;
4138 }
4139
4140 let name = CString::new(path_vec[vec_len - 1].to_str().unwrap()).unwrap();
4141
4142 fs.atomic_open(ctx, inode, &name, mode, flags, umask, security_ctx)
4143 }
4144
4145 fn symlink(
4146 fs: &PassthroughFs,
4147 linkname: &Path,
4148 path: &Path,
4149 security_ctx: Option<&CStr>,
4150 ) -> io::Result<Entry> {
4151 let parent = path.parent().unwrap();
4152 let filename = CString::new(path.file_name().unwrap().to_str().unwrap()).unwrap();
4153 let parent_inode = lookup(fs, parent)?;
4154 let ctx = get_context();
4155 let linkname = CString::new(linkname.to_str().unwrap()).unwrap();
4156 fs.symlink(ctx, &linkname, parent_inode, &filename, security_ctx)
4157 }
4158
4159 #[cfg(feature = "arc_quota")]
4161 fn fs_ioc_setpermission<R: io::Read>(
4162 fs: &PassthroughFs,
4163 in_size: u32,
4164 r: R,
4165 ) -> io::Result<IoctlReply> {
4166 let ctx = get_context();
4167 fs.ioctl(
4168 ctx,
4169 0,
4170 0,
4171 IoctlFlags::empty(),
4172 FS_IOC_SETPERMISSION as u32,
4173 0,
4174 in_size,
4175 0,
4176 r,
4177 )
4178 }
4179
4180 #[cfg(feature = "arc_quota")]
4182 fn fs_ioc_setpathxattr<R: io::Read>(
4183 fs: &PassthroughFs,
4184 in_size: u32,
4185 r: R,
4186 ) -> io::Result<IoctlReply> {
4187 let ctx = get_context();
4188 fs.ioctl(
4189 ctx,
4190 0,
4191 0,
4192 IoctlFlags::empty(),
4193 FS_IOC_SETPATHXATTR as u32,
4194 0,
4195 in_size,
4196 0,
4197 r,
4198 )
4199 }
4200
4201 #[test]
4202 fn rewrite_xattr_names() {
4203 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4206 let _guard = lock.lock().expect("acquire named lock");
4207
4208 let cfg = Config {
4209 rewrite_security_xattrs: true,
4210 ..Default::default()
4211 };
4212
4213 let p = PassthroughFs::new("tag", cfg).expect("Failed to create PassthroughFs");
4214
4215 let selinux = c"security.selinux";
4217 assert_eq!(p.rewrite_xattr_name(selinux).to_bytes(), selinux.to_bytes());
4218
4219 let user = c"user.foobar";
4221 assert_eq!(p.rewrite_xattr_name(user).to_bytes(), user.to_bytes());
4222 let trusted = c"trusted.foobar";
4223 assert_eq!(p.rewrite_xattr_name(trusted).to_bytes(), trusted.to_bytes());
4224 let system = c"system.foobar";
4225 assert_eq!(p.rewrite_xattr_name(system).to_bytes(), system.to_bytes());
4226
4227 let sehash = c"security.sehash";
4229 assert_eq!(
4230 p.rewrite_xattr_name(sehash).to_bytes(),
4231 b"user.virtiofs.security.sehash"
4232 );
4233 }
4234
4235 #[test]
4236 fn strip_xattr_names() {
4237 let only_nuls = b"\0\0\0\0\0";
4238 let mut actual = only_nuls.to_vec();
4239 strip_xattr_prefix(&mut actual);
4240 assert_eq!(&actual[..], &only_nuls[..]);
4241
4242 let no_nuls = b"security.sehashuser.virtiofs";
4243 let mut actual = no_nuls.to_vec();
4244 strip_xattr_prefix(&mut actual);
4245 assert_eq!(&actual[..], &no_nuls[..]);
4246
4247 let empty = b"";
4248 let mut actual = empty.to_vec();
4249 strip_xattr_prefix(&mut actual);
4250 assert_eq!(&actual[..], &empty[..]);
4251
4252 let no_strippable_names = b"security.selinux\0user.foobar\0system.test\0";
4253 let mut actual = no_strippable_names.to_vec();
4254 strip_xattr_prefix(&mut actual);
4255 assert_eq!(&actual[..], &no_strippable_names[..]);
4256
4257 let only_strippable_names = b"user.virtiofs.security.sehash\0user.virtiofs.security.wat\0";
4258 let mut actual = only_strippable_names.to_vec();
4259 strip_xattr_prefix(&mut actual);
4260 assert_eq!(&actual[..], b"security.sehash\0security.wat\0");
4261
4262 let mixed_names = b"user.virtiofs.security.sehash\0security.selinux\0user.virtiofs.security.wat\0user.foobar\0";
4263 let mut actual = mixed_names.to_vec();
4264 strip_xattr_prefix(&mut actual);
4265 let expected = b"security.sehash\0security.selinux\0security.wat\0user.foobar\0";
4266 assert_eq!(&actual[..], &expected[..]);
4267
4268 let no_nul_with_prefix = b"user.virtiofs.security.sehash";
4269 let mut actual = no_nul_with_prefix.to_vec();
4270 strip_xattr_prefix(&mut actual);
4271 assert_eq!(&actual[..], b"security.sehash");
4272 }
4273
4274 #[test]
4275 fn lookup_files() {
4276 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4279 let _guard = lock.lock().expect("acquire named lock");
4280
4281 let temp_dir = TempDir::new().unwrap();
4282 create_test_data(&temp_dir, &["dir"], &["a.txt", "dir/b.txt"]);
4283
4284 let cfg = Default::default();
4285 let fs = PassthroughFs::new("tag", cfg).unwrap();
4286
4287 let capable = FsOptions::empty();
4288 fs.init(capable).unwrap();
4289
4290 assert!(lookup(&fs, &temp_dir.path().join("a.txt")).is_ok());
4291 assert!(lookup(&fs, &temp_dir.path().join("dir")).is_ok());
4292 assert!(lookup(&fs, &temp_dir.path().join("dir/b.txt")).is_ok());
4293
4294 assert_eq!(
4295 lookup(&fs, &temp_dir.path().join("nonexistent-file"))
4296 .expect_err("file must not exist")
4297 .kind(),
4298 io::ErrorKind::NotFound
4299 );
4300 assert_eq!(
4302 lookup(&fs, &temp_dir.path().join("A.txt"))
4303 .expect_err("file must not exist")
4304 .kind(),
4305 io::ErrorKind::NotFound
4306 );
4307 }
4308
4309 #[test]
4310 fn lookup_files_ascii_casefold() {
4311 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4314 let _guard = lock.lock().expect("acquire named lock");
4315
4316 let temp_dir = TempDir::new().unwrap();
4317 create_test_data(&temp_dir, &["dir"], &["a.txt", "dir/b.txt"]);
4318
4319 let cfg = Config {
4320 ascii_casefold: true,
4321 ..Default::default()
4322 };
4323 let fs = PassthroughFs::new("tag", cfg).unwrap();
4324
4325 let capable = FsOptions::empty();
4326 fs.init(capable).unwrap();
4327
4328 let a_inode = lookup(&fs, &temp_dir.path().join("a.txt")).expect("a.txt must be found");
4330 assert_eq!(
4331 lookup(&fs, &temp_dir.path().join("A.txt")).expect("A.txt must exist"),
4332 a_inode
4333 );
4334
4335 let dir_inode = lookup(&fs, &temp_dir.path().join("dir")).expect("dir must be found");
4336 assert_eq!(
4337 lookup(&fs, &temp_dir.path().join("DiR")).expect("DiR must exist"),
4338 dir_inode
4339 );
4340
4341 let b_inode =
4342 lookup(&fs, &temp_dir.path().join("dir/b.txt")).expect("dir/b.txt must be found");
4343 assert_eq!(
4344 lookup(&fs, &temp_dir.path().join("dIr/B.TxT")).expect("dIr/B.TxT must exist"),
4345 b_inode
4346 );
4347
4348 assert_eq!(
4349 lookup(&fs, &temp_dir.path().join("nonexistent-file"))
4350 .expect_err("file must not exist")
4351 .kind(),
4352 io::ErrorKind::NotFound
4353 );
4354 }
4355
4356 fn test_create_and_remove(ascii_casefold: bool) {
4357 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4360 let _guard = lock.lock().expect("acquire named lock");
4361
4362 let temp_dir = TempDir::new().unwrap();
4363 let timeout = Duration::from_millis(10);
4364 let cfg = Config {
4365 timeout,
4366 cache_policy: CachePolicy::Auto,
4367 ascii_casefold,
4368 ..Default::default()
4369 };
4370 let fs = PassthroughFs::new("tag", cfg).unwrap();
4371
4372 let capable = FsOptions::empty();
4373 fs.init(capable).unwrap();
4374
4375 let a_path = temp_dir.path().join("a.txt");
4377 let b_path = temp_dir.path().join("b.txt");
4378 let a_entry = create(&fs, &a_path).expect("create a.txt");
4379 let b_entry = create(&fs, &b_path).expect("create b.txt");
4380 assert_eq!(
4381 a_entry.inode,
4382 lookup(&fs, &a_path).expect("lookup a.txt"),
4383 "Created file 'a.txt' must be looked up"
4384 );
4385 assert_eq!(
4386 b_entry.inode,
4387 lookup(&fs, &b_path).expect("lookup b.txt"),
4388 "Created file 'b.txt' must be looked up"
4389 );
4390
4391 unlink(&fs, &a_path).expect("Remove");
4393 assert_eq!(
4394 lookup(&fs, &a_path)
4395 .expect_err("file must not exist")
4396 .kind(),
4397 io::ErrorKind::NotFound,
4398 "a.txt must be removed"
4399 );
4400 let upper_a_path = temp_dir.path().join("A.TXT");
4402 assert_eq!(
4403 lookup(&fs, &upper_a_path)
4404 .expect_err("file must not exist")
4405 .kind(),
4406 io::ErrorKind::NotFound,
4407 "A.txt must be removed"
4408 );
4409
4410 assert!(!a_path.exists(), "a.txt must be removed");
4412 assert!(b_path.exists(), "b.txt must exist");
4413 }
4414
4415 #[test]
4416 fn create_and_remove() {
4417 test_create_and_remove(false );
4418 }
4419
4420 #[test]
4421 fn create_and_remove_casefold() {
4422 test_create_and_remove(true );
4423 }
4424
4425 fn test_create_and_forget(ascii_casefold: bool) {
4426 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4429 let _guard = lock.lock().expect("acquire named lock");
4430
4431 let temp_dir = TempDir::new().unwrap();
4432 let timeout = Duration::from_millis(10);
4433 let cfg = Config {
4434 timeout,
4435 cache_policy: CachePolicy::Auto,
4436 ascii_casefold,
4437 ..Default::default()
4438 };
4439 let fs = PassthroughFs::new("tag", cfg).unwrap();
4440
4441 let capable = FsOptions::empty();
4442 fs.init(capable).unwrap();
4443
4444 let a_path = temp_dir.path().join("a.txt");
4446 let a_entry = create(&fs, &a_path).expect("create a.txt");
4447 assert_eq!(
4448 a_entry.inode,
4449 lookup(&fs, &a_path).expect("lookup a.txt"),
4450 "Created file 'a.txt' must be looked up"
4451 );
4452
4453 forget(&fs, &a_path).expect("forget a.txt");
4455
4456 if ascii_casefold {
4457 let upper_a_path = temp_dir.path().join("A.TXT");
4458 let new_a_inode = lookup(&fs, &upper_a_path).expect("lookup a.txt");
4459 assert_ne!(
4460 a_entry.inode, new_a_inode,
4461 "inode must be changed after forget()"
4462 );
4463 assert_eq!(
4464 new_a_inode,
4465 lookup(&fs, &a_path).expect("lookup a.txt"),
4466 "inode must be same for a.txt and A.TXT"
4467 );
4468 } else {
4469 assert_ne!(
4470 a_entry.inode,
4471 lookup(&fs, &a_path).expect("lookup a.txt"),
4472 "inode must be changed after forget()"
4473 );
4474 }
4475 }
4476
4477 #[test]
4478 fn create_and_forget() {
4479 test_create_and_forget(false );
4480 }
4481
4482 #[test]
4483 fn create_and_forget_casefold() {
4484 test_create_and_forget(true );
4485 }
4486
4487 #[test]
4488 fn casefold_lookup_cache() {
4489 let temp_dir = TempDir::new().unwrap();
4490 create_test_data(&temp_dir, &[], &["a.txt"]);
4492
4493 let cfg = Config {
4494 ascii_casefold: true,
4495 ..Default::default()
4496 };
4497 let fs = PassthroughFs::new("tag", cfg).unwrap();
4498
4499 let capable = FsOptions::empty();
4500 fs.init(capable).unwrap();
4501
4502 let parent = lookup(&fs, temp_dir.path()).expect("lookup temp_dir");
4503
4504 let large_a_path = temp_dir.path().join("A.TXT");
4506 lookup(&fs, &large_a_path).expect("A.TXT must exist");
4508 assert!(fs.exists_in_casefold_cache(parent, &CString::new("A.TXT").unwrap()));
4509
4510 let b_path = temp_dir.path().join("b.txt");
4512 create(&fs, &b_path).expect("create b.txt");
4513 assert!(fs.exists_in_casefold_cache(parent, &CString::new("B.TXT").unwrap()));
4515 unlink(&fs, &b_path).expect("remove b.txt");
4517 assert!(!fs.exists_in_casefold_cache(parent, &CString::new("B.TXT").unwrap()));
4518 }
4519
4520 #[test]
4521 fn lookup_negative_cache() {
4522 let temp_dir = TempDir::new().unwrap();
4523 create_test_data(&temp_dir, &[], &[]);
4525
4526 let cfg = Config {
4527 negative_timeout: Duration::from_secs(5),
4528 ..Default::default()
4529 };
4530 let fs = PassthroughFs::new("tag", cfg).unwrap();
4531
4532 let capable = FsOptions::empty();
4533 fs.init(capable).unwrap();
4534
4535 let a_path = temp_dir.path().join("a.txt");
4536 assert_eq!(
4539 0,
4540 lookup(&fs, &a_path).expect("lookup a.txt"),
4541 "Entry with inode=0 is expected for non-existing file 'a.txt'"
4542 );
4543 let a_entry = create(&fs, &a_path).expect("create a.txt");
4545 assert_eq!(
4546 a_entry.inode,
4547 lookup(&fs, &a_path).expect("lookup a.txt"),
4548 "Created file 'a.txt' must be looked up"
4549 );
4550 unlink(&fs, &a_path).expect("Remove");
4552 assert_eq!(
4553 0,
4554 lookup(&fs, &a_path).expect("lookup a.txt"),
4555 "Entry with inode=0 is expected for the removed file 'a.txt'"
4556 );
4557 }
4558 #[test]
4559 fn test_atomic_open_existing_file() {
4560 atomic_open_existing_file(false);
4561 }
4562
4563 #[test]
4564 fn test_atomic_open_existing_file_zero_message() {
4565 atomic_open_existing_file(true);
4566 }
4567
4568 fn atomic_open_existing_file(zero_message_open: bool) {
4569 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4572 let _guard = lock.lock().expect("acquire named lock");
4573
4574 let temp_dir = TempDir::new().unwrap();
4575 create_test_data(&temp_dir, &["dir"], &["a.txt", "dir/b.txt", "dir/c.txt"]);
4576
4577 let cache_policy = match zero_message_open {
4578 true => CachePolicy::Always,
4579 false => CachePolicy::Auto,
4580 };
4581
4582 let cfg = Config {
4583 cache_policy,
4584 ..Default::default()
4585 };
4586 let fs = PassthroughFs::new("tag", cfg).unwrap();
4587
4588 let capable = FsOptions::ZERO_MESSAGE_OPEN;
4589 fs.init(capable).unwrap();
4590
4591 let res = atomic_open(
4593 &fs,
4594 &temp_dir.path().join("a.txt"),
4595 0o666,
4596 libc::O_RDWR as u32,
4597 0,
4598 None,
4599 );
4600 assert!(res.is_ok());
4601 let (entry, handler, open_options) = res.unwrap();
4602 assert_ne!(entry.inode, 0);
4603
4604 if zero_message_open {
4605 assert!(handler.is_none());
4606 assert_eq!(open_options, OpenOptions::KEEP_CACHE);
4607 } else {
4608 assert!(handler.is_some());
4609 assert_ne!(
4610 open_options & OpenOptions::FILE_CREATED,
4611 OpenOptions::FILE_CREATED
4612 );
4613 }
4614
4615 let res = atomic_open(
4617 &fs,
4618 &temp_dir.path().join("dir/b.txt"),
4619 0o666,
4620 (libc::O_RDWR | libc::O_CREAT) as u32,
4621 0,
4622 None,
4623 );
4624 assert!(res.is_ok());
4625 let (entry, handler, open_options) = res.unwrap();
4626 assert_ne!(entry.inode, 0);
4627
4628 if zero_message_open {
4629 assert!(handler.is_none());
4630 assert_eq!(open_options, OpenOptions::KEEP_CACHE);
4631 } else {
4632 assert!(handler.is_some());
4633 assert_ne!(
4634 open_options & OpenOptions::FILE_CREATED,
4635 OpenOptions::FILE_CREATED
4636 );
4637 }
4638
4639 let res = atomic_open(
4642 &fs,
4643 &temp_dir.path().join("dir/c.txt"),
4644 0o666,
4645 (libc::O_RDWR | libc::O_CREAT | libc::O_EXCL) as u32,
4646 0,
4647 None,
4648 );
4649 assert!(res.is_err());
4650 let err_kind = res.unwrap_err().kind();
4651 assert_eq!(err_kind, io::ErrorKind::AlreadyExists);
4652 }
4653
4654 #[test]
4655 fn test_atomic_open_non_existing_file() {
4656 atomic_open_non_existing_file(false);
4657 }
4658
4659 #[test]
4660 fn test_atomic_open_non_existing_file_zero_message() {
4661 atomic_open_non_existing_file(true);
4662 }
4663
4664 fn atomic_open_non_existing_file(zero_message_open: bool) {
4665 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4668 let _guard = lock.lock().expect("acquire named lock");
4669
4670 let temp_dir = TempDir::new().unwrap();
4671
4672 let cache_policy = match zero_message_open {
4673 true => CachePolicy::Always,
4674 false => CachePolicy::Auto,
4675 };
4676
4677 let cfg = Config {
4678 cache_policy,
4679 ..Default::default()
4680 };
4681 let fs = PassthroughFs::new("tag", cfg).unwrap();
4682
4683 let capable = FsOptions::ZERO_MESSAGE_OPEN;
4684 fs.init(capable).unwrap();
4685
4686 let res = atomic_open(
4688 &fs,
4689 &temp_dir.path().join("a.txt"),
4690 0o666,
4691 libc::O_RDWR as u32,
4692 0,
4693 None,
4694 );
4695 assert!(res.is_err());
4696 let err_kind = res.unwrap_err().kind();
4697 assert_eq!(err_kind, io::ErrorKind::NotFound);
4698
4699 let res = atomic_open(
4701 &fs,
4702 &temp_dir.path().join("b.txt"),
4703 0o666,
4704 (libc::O_RDWR | libc::O_CREAT) as u32,
4705 0,
4706 None,
4707 );
4708 assert!(res.is_ok());
4709 let (entry, handler, open_options) = res.unwrap();
4710 assert_ne!(entry.inode, 0);
4711
4712 if zero_message_open {
4713 assert!(handler.is_none());
4714 assert_eq!(
4715 open_options & OpenOptions::KEEP_CACHE,
4716 OpenOptions::KEEP_CACHE
4717 );
4718 } else {
4719 assert!(handler.is_some());
4720 }
4721 assert_eq!(
4722 open_options & OpenOptions::FILE_CREATED,
4723 OpenOptions::FILE_CREATED
4724 );
4725 }
4726
4727 #[test]
4728 fn atomic_open_symbol_link() {
4729 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4732 let _guard = lock.lock().expect("acquire named lock");
4733
4734 let temp_dir = TempDir::new().unwrap();
4735 create_test_data(&temp_dir, &["dir"], &["a.txt"]);
4736
4737 let cfg = Default::default();
4738 let fs = PassthroughFs::new("tag", cfg).unwrap();
4739
4740 let capable = FsOptions::empty();
4741 fs.init(capable).unwrap();
4742
4743 let res_dst = atomic_open(
4745 &fs,
4746 &temp_dir.path().join("a.txt"),
4747 0o666,
4748 libc::O_RDWR as u32,
4749 0,
4750 None,
4751 );
4752 assert!(res_dst.is_ok());
4753 let (entry_dst, handler_dst, _) = res_dst.unwrap();
4754 assert_ne!(entry_dst.inode, 0);
4755 assert!(handler_dst.is_some());
4756
4757 let sym1_res = symlink(
4759 &fs,
4760 &temp_dir.path().join("a.txt"),
4761 &temp_dir.path().join("blink"),
4762 None,
4763 );
4764 assert!(sym1_res.is_ok());
4765 let sym1_entry = sym1_res.unwrap();
4766 assert_ne!(sym1_entry.inode, 0);
4767
4768 let res = atomic_open(
4770 &fs,
4771 &temp_dir.path().join("blink"),
4772 0o666,
4773 libc::O_RDWR as u32,
4774 0,
4775 None,
4776 );
4777 assert!(res.is_ok());
4778 let (entry, handler, open_options) = res.unwrap();
4779 assert_eq!(entry.inode, sym1_entry.inode);
4780 assert!(handler.is_none());
4781 assert_eq!(open_options, OpenOptions::empty());
4782
4783 unlink(&fs, &temp_dir.path().join("a.txt")).expect("Remove");
4785 assert_eq!(
4786 lookup(&fs, &temp_dir.path().join("a.txt"))
4787 .expect_err("file must not exist")
4788 .kind(),
4789 io::ErrorKind::NotFound,
4790 "a.txt must be removed"
4791 );
4792
4793 let res = atomic_open(
4795 &fs,
4796 &temp_dir.path().join("blink"),
4797 0o666,
4798 libc::O_RDWR as u32,
4799 0,
4800 None,
4801 );
4802 assert!(res.is_ok());
4803 let (entry, handler, open_options) = res.unwrap();
4804 assert_eq!(entry.inode, sym1_entry.inode);
4805 assert!(handler.is_none());
4806 assert_eq!(open_options, OpenOptions::empty());
4807 }
4808
4809 #[test]
4810 #[cfg(feature = "arc_quota")]
4811 fn set_permission_ioctl_valid_data() {
4812 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4815 let _guard = lock.lock().expect("acquire named lock");
4816
4817 let cfg = Config {
4818 max_dynamic_perm: 1,
4819 ..Default::default()
4820 };
4821 let p = PassthroughFs::new("tag", cfg).expect("Failed to create PassthroughFs");
4822
4823 let perm_path_string = String::from("/test");
4824 let fs_permission_data_buffer = FsPermissionDataBuffer {
4825 guest_uid: 1,
4826 guest_gid: 2,
4827 host_uid: 3,
4828 host_gid: 4,
4829 umask: 5,
4830 pad: 0,
4831 perm_path: {
4832 let mut perm_path: [u8; FS_IOCTL_PATH_MAX_LEN] = [0; FS_IOCTL_PATH_MAX_LEN];
4833 perm_path[..perm_path_string.len()].copy_from_slice(perm_path_string.as_bytes());
4834 perm_path
4835 },
4836 };
4837 let r = std::io::Cursor::new(fs_permission_data_buffer.as_bytes());
4838
4839 let res = fs_ioc_setpermission(
4840 &p,
4841 mem::size_of_val(&fs_permission_data_buffer) as u32,
4842 r.clone(),
4843 )
4844 .expect("valid input should get IoctlReply");
4845 assert!(matches!(res, IoctlReply::Done(Ok(data)) if data.is_empty()));
4846
4847 let read_guard = p
4848 .permission_paths
4849 .read()
4850 .expect("read permission_paths failed");
4851 let permission_data = read_guard
4852 .first()
4853 .expect("permission path should not be empty");
4854
4855 let expected_data = PermissionData {
4857 guest_uid: 1,
4858 guest_gid: 2,
4859 host_uid: 3,
4860 host_gid: 4,
4861 umask: 5,
4862 perm_path: perm_path_string,
4863 };
4864 assert_eq!(*permission_data, expected_data);
4865
4866 let res = fs_ioc_setpermission(
4868 &p,
4869 mem::size_of_val(&fs_permission_data_buffer) as u32,
4870 r.clone(),
4871 )
4872 .expect("valid input should get IoctlReply");
4873 assert!(
4874 matches!(res, IoctlReply::Done(Err(err)) if err.raw_os_error().is_some_and(|errno| {
4875 errno == libc::EPERM
4876 }))
4877 );
4878 }
4879
4880 #[test]
4881 #[cfg(feature = "arc_quota")]
4882 fn set_permission_ioctl_invalid_data() {
4883 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4886 let _guard = lock.lock().expect("acquire named lock");
4887
4888 let cfg = Config {
4889 max_dynamic_perm: 1,
4890 ..Default::default()
4891 };
4892 let p = PassthroughFs::new("tag", cfg).expect("Failed to create PassthroughFs");
4893
4894 let perm_path_string = String::from("test");
4896 let fs_permission_data_buffer = FsPermissionDataBuffer {
4897 guest_uid: 1,
4898 guest_gid: 2,
4899 host_uid: 3,
4900 host_gid: 4,
4901 umask: 5,
4902 pad: 0,
4903 perm_path: {
4904 let mut perm_path: [u8; FS_IOCTL_PATH_MAX_LEN] = [0; FS_IOCTL_PATH_MAX_LEN];
4905 perm_path[..perm_path_string.len()].copy_from_slice(perm_path_string.as_bytes());
4906 perm_path
4907 },
4908 };
4909
4910 let r = std::io::Cursor::new(fs_permission_data_buffer.as_bytes());
4911 let res = fs_ioc_setpermission(&p, mem::size_of_val(&fs_permission_data_buffer) as u32, r)
4914 .expect("invalid perm_path should get IoctlReply");
4915 assert!(
4916 matches!(res, IoctlReply::Done(Err(err)) if err.raw_os_error().is_some_and(|errno| {
4917 errno == libc::EINVAL
4918 }))
4919 );
4920
4921 let fake_data_buffer: [u8; 128] = [0; 128];
4922 let r = std::io::Cursor::new(fake_data_buffer.as_bytes());
4923
4924 let res = fs_ioc_setpermission(&p, mem::size_of_val(&fake_data_buffer) as u32, r)
4927 .expect_err("invalid in_size should get Error");
4928 assert!(res
4929 .raw_os_error()
4930 .is_some_and(|errno| { errno == libc::EINVAL }));
4931 }
4932
4933 #[test]
4934 #[cfg(feature = "arc_quota")]
4935 fn permission_data_path_matching() {
4936 let ctx = get_context();
4937 let temp_dir = TempDir::new().unwrap();
4938 create_test_data(&temp_dir, &["dir"], &["a.txt", "dir/a.txt"]);
4940
4941 let cfg = Config {
4942 max_dynamic_perm: 1,
4943 ..Default::default()
4944 };
4945 let fs = PassthroughFs::new("tag", cfg).unwrap();
4946
4947 let capable = FsOptions::empty();
4948 fs.init(capable).unwrap();
4949
4950 const BY_PATH_UID: u32 = 655360;
4951 const BY_PATH_GID: u32 = 655361;
4952 const BY_PATH_UMASK: u32 = 0o007;
4953
4954 let dir_path = temp_dir.path().join("dir");
4955 let permission_data = PermissionData {
4956 guest_uid: BY_PATH_UID,
4957 guest_gid: BY_PATH_GID,
4958 host_uid: ctx.uid,
4959 host_gid: ctx.gid,
4960 umask: BY_PATH_UMASK,
4961 perm_path: dir_path.to_string_lossy().into_owned(),
4962 };
4963 fs.permission_paths
4964 .write()
4965 .expect("permission_path lock must be acquired")
4966 .push(permission_data);
4967
4968 let a_path = temp_dir.path().join("a.txt");
4970 let in_dir_a_path = dir_path.join("a.txt");
4971
4972 let a_entry = lookup_ent(&fs, &a_path).expect("a.txt must exist");
4974 assert_ne!(a_entry.attr.st_uid, BY_PATH_UID);
4975 assert_ne!(a_entry.attr.st_gid, BY_PATH_GID);
4976
4977 let in_dir_a_entry = lookup_ent(&fs, &in_dir_a_path).expect("dir/a.txt must exist");
4979 assert_eq!(in_dir_a_entry.attr.st_uid, BY_PATH_UID);
4980 assert_eq!(in_dir_a_entry.attr.st_gid, BY_PATH_GID);
4981 assert_eq!(in_dir_a_entry.attr.st_mode & 0o777, !BY_PATH_UMASK & 0o777);
4982
4983 let in_dir_b_path = dir_path.join("b.txt");
4985 create(&fs, &in_dir_b_path).expect("create b.txt");
4986
4987 let in_dir_b_entry = lookup_ent(&fs, &in_dir_a_path).expect("dir/b.txt must exist");
4989 assert_eq!(in_dir_b_entry.attr.st_uid, BY_PATH_UID);
4990 assert_eq!(in_dir_b_entry.attr.st_gid, BY_PATH_GID);
4991 assert_eq!(in_dir_b_entry.attr.st_mode & 0o777, !BY_PATH_UMASK & 0o777);
4992 }
4993
4994 #[test]
4995 #[cfg(feature = "fs_permission_translation")]
4996 fn test_copy_file_range_path_mapping() {
4997 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
4998 let _guard = lock.lock().expect("acquire named lock");
4999
5000 let real_ctx = get_context();
5001 let temp_dir = TempDir::new().unwrap();
5002 let dir_path = temp_dir.path().join("dir");
5003 create_test_data(&temp_dir, &["dir"], &["src.txt", "dir/dst.txt"]);
5004
5005 let cfg = Config {
5006 ..Default::default()
5007 };
5008 let fs = PassthroughFs::new("tag", cfg).unwrap();
5009 fs.init(FsOptions::empty()).unwrap();
5010
5011 let mut fake_ctx = real_ctx;
5013 fake_ctx.uid = 9999;
5014 fake_ctx.gid = 9999;
5015
5016 let permission_data = PermissionData {
5021 guest_uid: fake_ctx.uid,
5022 guest_gid: fake_ctx.gid,
5023 host_uid: real_ctx.uid,
5024 host_gid: real_ctx.gid,
5025 umask: 0,
5026 perm_path: dir_path.to_string_lossy().into_owned(),
5027 };
5028 fs.permission_paths.write().unwrap().push(permission_data);
5029
5030 let src_path = temp_dir.path().join("src.txt");
5031 let dst_path = dir_path.join("dst.txt");
5032
5033 std::fs::write(&src_path, b"hello world").unwrap();
5034
5035 let src_inode = lookup(&fs, &src_path).unwrap();
5036 let dst_inode = lookup(&fs, &dst_path).unwrap();
5037
5038 let (src_handle, _) = fs
5042 .open(real_ctx, src_inode, libc::O_RDONLY as u32)
5043 .expect("open src");
5044 let (dst_handle, _) = fs
5045 .open(real_ctx, dst_inode, libc::O_WRONLY as u32)
5046 .expect("open dst");
5047
5048 let src_handle = src_handle.unwrap();
5049 let dst_handle = dst_handle.unwrap();
5050
5051 let result = fs.copy_file_range(
5054 fake_ctx, src_inode, src_handle, 0, dst_inode, dst_handle, 0, 5, 0,
5055 );
5056
5057 assert!(
5058 result.is_ok(),
5059 "copy_file_range failed: {:?}. Mapping might not be applied.",
5060 result.err()
5061 );
5062 assert_eq!(result.unwrap(), 5);
5063
5064 let content = std::fs::read(&dst_path).unwrap();
5065 assert_eq!(&content[0..5], b"hello");
5066 }
5067
5068 #[test]
5069 #[cfg(feature = "arc_quota")]
5070 fn set_path_xattr_ioctl_valid_data() {
5071 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5074 let _guard = lock.lock().expect("acquire named lock");
5075
5076 let cfg: Config = Config {
5077 max_dynamic_xattr: 1,
5078 ..Default::default()
5079 };
5080 let p = PassthroughFs::new("tag", cfg).expect("Failed to create PassthroughFs");
5081
5082 let path_string = String::from("/test");
5083 let xattr_name_string = String::from("test_name");
5084 let xattr_value_string = String::from("test_value");
5085 let fs_path_xattr_data_buffer = FsPathXattrDataBuffer {
5086 path: {
5087 let mut path: [u8; FS_IOCTL_PATH_MAX_LEN] = [0; FS_IOCTL_PATH_MAX_LEN];
5088 path[..path_string.len()].copy_from_slice(path_string.as_bytes());
5089 path
5090 },
5091 xattr_name: {
5092 let mut xattr_name: [u8; FS_IOCTL_XATTR_NAME_MAX_LEN] =
5093 [0; FS_IOCTL_XATTR_NAME_MAX_LEN];
5094 xattr_name[..xattr_name_string.len()].copy_from_slice(xattr_name_string.as_bytes());
5095 xattr_name
5096 },
5097 xattr_value: {
5098 let mut xattr_value: [u8; FS_IOCTL_XATTR_VALUE_MAX_LEN] =
5099 [0; FS_IOCTL_XATTR_VALUE_MAX_LEN];
5100 xattr_value[..xattr_value_string.len()]
5101 .copy_from_slice(xattr_value_string.as_bytes());
5102 xattr_value
5103 },
5104 };
5105 let r = std::io::Cursor::new(fs_path_xattr_data_buffer.as_bytes());
5106
5107 let res = fs_ioc_setpathxattr(
5108 &p,
5109 mem::size_of_val(&fs_path_xattr_data_buffer) as u32,
5110 r.clone(),
5111 )
5112 .expect("valid input should get IoctlReply");
5113 assert!(matches!(res, IoctlReply::Done(Ok(data)) if data.is_empty()));
5114
5115 let read_guard = p.xattr_paths.read().expect("read xattr_paths failed");
5116 let xattr_data = read_guard.first().expect("xattr_paths should not be empty");
5117
5118 let expected_data = XattrData {
5120 xattr_path: path_string,
5121 xattr_name: xattr_name_string,
5122 xattr_value: xattr_value_string,
5123 };
5124 assert_eq!(*xattr_data, expected_data);
5125
5126 let res = fs_ioc_setpathxattr(
5128 &p,
5129 mem::size_of_val(&fs_path_xattr_data_buffer) as u32,
5130 r.clone(),
5131 )
5132 .expect("valid input should get IoctlReply");
5133 assert!(
5134 matches!(res, IoctlReply::Done(Err(err)) if err.raw_os_error().is_some_and(|errno| {
5135 errno == libc::EPERM
5136 }))
5137 );
5138 }
5139 #[test]
5140 #[cfg(feature = "arc_quota")]
5141 fn set_path_xattr_ioctl_invalid_data() {
5142 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5145 let _guard = lock.lock().expect("acquire named lock");
5146
5147 let cfg: Config = Config {
5148 max_dynamic_xattr: 1,
5149 ..Default::default()
5150 };
5151 let p = PassthroughFs::new("tag", cfg).expect("Failed to create PassthroughFs");
5152
5153 let path_string = String::from("test");
5154 let xattr_name_string = String::from("test_name");
5155 let xattr_value_string = String::from("test_value");
5156 let fs_path_xattr_data_buffer = FsPathXattrDataBuffer {
5157 path: {
5158 let mut path: [u8; FS_IOCTL_PATH_MAX_LEN] = [0; FS_IOCTL_PATH_MAX_LEN];
5159 path[..path_string.len()].copy_from_slice(path_string.as_bytes());
5160 path
5161 },
5162 xattr_name: {
5163 let mut xattr_name: [u8; FS_IOCTL_XATTR_NAME_MAX_LEN] =
5164 [0; FS_IOCTL_XATTR_NAME_MAX_LEN];
5165 xattr_name[..xattr_name_string.len()].copy_from_slice(xattr_name_string.as_bytes());
5166 xattr_name
5167 },
5168 xattr_value: {
5169 let mut xattr_value: [u8; FS_IOCTL_XATTR_VALUE_MAX_LEN] =
5170 [0; FS_IOCTL_XATTR_VALUE_MAX_LEN];
5171 xattr_value[..xattr_value_string.len()]
5172 .copy_from_slice(xattr_value_string.as_bytes());
5173 xattr_value
5174 },
5175 };
5176 let r = std::io::Cursor::new(fs_path_xattr_data_buffer.as_bytes());
5177
5178 let res = fs_ioc_setpathxattr(
5180 &p,
5181 mem::size_of_val(&fs_path_xattr_data_buffer) as u32,
5182 r.clone(),
5183 )
5184 .expect("valid input should get IoctlReply");
5185 assert!(
5186 matches!(res, IoctlReply::Done(Err(err)) if err.raw_os_error().is_some_and(|errno| {
5187 errno == libc::EINVAL
5188 }))
5189 );
5190
5191 let fake_data_buffer: [u8; 128] = [0; 128];
5192 let r = std::io::Cursor::new(fake_data_buffer.as_bytes());
5193 let res = fs_ioc_setpathxattr(&p, mem::size_of_val(&fake_data_buffer) as u32, r.clone())
5196 .expect_err("valid input should get IoctlReply");
5197 assert!(res
5198 .raw_os_error()
5199 .is_some_and(|errno| { errno == libc::EINVAL }));
5200 }
5201
5202 #[test]
5203 #[cfg(feature = "arc_quota")]
5204 fn xattr_data_path_matching() {
5205 let ctx = get_context();
5206 let temp_dir = TempDir::new().unwrap();
5207 create_test_data(&temp_dir, &["dir"], &["a.txt", "dir/a.txt"]);
5209
5210 let cfg = Config {
5211 max_dynamic_xattr: 1,
5212 ..Default::default()
5213 };
5214 let fs = PassthroughFs::new("tag", cfg).unwrap();
5215
5216 let capable = FsOptions::empty();
5217 fs.init(capable).unwrap();
5218
5219 let dir_path = temp_dir.path().join("dir");
5220 let xattr_name_string = String::from("test_name");
5221 let xattr_name_cstring = CString::new(xattr_name_string.clone()).expect("create c string");
5222 let xattr_value_string = String::from("test_value");
5223 let xattr_value_bytes = xattr_value_string.clone().into_bytes();
5224
5225 let xattr_data = XattrData {
5226 xattr_name: xattr_name_string,
5227 xattr_value: xattr_value_string,
5228 xattr_path: dir_path.to_string_lossy().into_owned(),
5229 };
5230 fs.xattr_paths
5231 .write()
5232 .expect("xattr_paths lock must be acquired")
5233 .push(xattr_data);
5234
5235 let a_path: std::path::PathBuf = temp_dir.path().join("a.txt");
5237 let in_dir_a_path = dir_path.join("a.txt");
5238
5239 let a_node = lookup(&fs, a_path.as_path()).expect("lookup a node");
5240 assert!(fs
5242 .getxattr(
5243 ctx,
5244 a_node,
5245 &xattr_name_cstring,
5246 xattr_value_bytes.len() as u32
5247 )
5248 .is_err());
5249
5250 let in_dir_a_node = lookup(&fs, in_dir_a_path.as_path()).expect("lookup in dir a node");
5251 let in_dir_a_reply = fs
5253 .getxattr(
5254 ctx,
5255 in_dir_a_node,
5256 &xattr_name_cstring,
5257 xattr_value_bytes.len() as u32,
5258 )
5259 .expect("Getxattr should success");
5260 assert!(matches!(in_dir_a_reply, GetxattrReply::Value(v) if v == xattr_value_bytes));
5261 let in_dir_b_path = dir_path.join("b.txt");
5263 create(&fs, &in_dir_b_path).expect("create b.txt");
5264
5265 let in_dir_b_node = lookup(&fs, in_dir_a_path.as_path()).expect("lookup in dir b node");
5267 let in_dir_b_reply = fs
5268 .getxattr(
5269 ctx,
5270 in_dir_b_node,
5271 &xattr_name_cstring,
5272 xattr_value_bytes.len() as u32,
5273 )
5274 .expect("Getxattr should success");
5275 assert!(matches!(in_dir_b_reply, GetxattrReply::Value(v) if v == xattr_value_bytes));
5276 }
5277
5278 fn atomic_open_create_o_append(writeback: bool) {
5281 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5284 let _guard = lock.lock().expect("acquire named lock");
5285
5286 let temp_dir = TempDir::new().unwrap();
5287
5288 let cfg = Config {
5289 cache_policy: CachePolicy::Always,
5290 writeback,
5291 ..Default::default()
5292 };
5293 let fs = PassthroughFs::new("tag", cfg).unwrap();
5294
5295 let capable = FsOptions::ZERO_MESSAGE_OPEN | FsOptions::WRITEBACK_CACHE;
5296 fs.init(capable).unwrap();
5297
5298 let (entry, _, _) = atomic_open(
5299 &fs,
5300 &temp_dir.path().join("a.txt"),
5301 0o666,
5302 (libc::O_RDWR | libc::O_CREAT | libc::O_APPEND) as u32,
5303 0,
5304 None,
5305 )
5306 .expect("atomic_open");
5307 assert_ne!(entry.inode, 0);
5308
5309 let inodes = fs.inodes.lock();
5310 let data = inodes.get(&entry.inode).unwrap();
5311 let flags = data.file.lock().open_flags;
5312 if writeback {
5313 assert_eq!(flags & libc::O_APPEND, 0);
5316 } else {
5317 assert_eq!(flags & libc::O_APPEND, libc::O_APPEND);
5319 }
5320 }
5321
5322 #[test]
5323 fn test_atomic_open_create_o_append_no_writeback() {
5324 atomic_open_create_o_append(false);
5325 }
5326
5327 #[test]
5328 fn test_atomic_open_create_o_append_writeback() {
5329 atomic_open_create_o_append(true);
5330 }
5331
5332 #[test]
5333 fn test_lookup_dotdot_escape() {
5334 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5335 let _guard = lock.lock().expect("acquire named lock");
5336 let temp_dir = TempDir::new().unwrap();
5337 let root_path = temp_dir.path().join("root");
5338 std::fs::create_dir(&root_path).unwrap();
5339
5340 let secret_file = temp_dir.path().join("secret.txt");
5342 std::fs::write(&secret_file, "top secret").unwrap();
5343
5344 let cfg = Config {
5345 ..Default::default()
5346 };
5347 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
5348 fs.set_root_dir(root_path.to_str().unwrap().to_string())
5349 .unwrap();
5350 fs.init(FsOptions::empty()).unwrap();
5351 let ctx = get_context();
5352
5353 let dotdot = c"..";
5355 let res = fs.lookup(ctx, 1, dotdot);
5356 assert!(res.is_err(), "Lookup .. should be blocked!");
5357 }
5358
5359 #[test]
5360 fn test_passthrough_fs_create_validation() {
5361 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5362 let _guard = lock.lock().expect("acquire named lock");
5363
5364 let temp_dir = TempDir::new().unwrap();
5365 create_test_data(&temp_dir, &["allowed"], &[]);
5366
5367 let cfg = Default::default();
5368 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
5369 fs.init(FsOptions::empty()).unwrap();
5370
5371 let allowlist = Arc::new(RwLock::new(PathAllowlist::new()));
5372 fs.set_allowlist(Some(allowlist.clone()));
5373
5374 allowlist.write().unwrap().add_path(
5375 temp_dir
5376 .path()
5377 .join("allowed")
5378 .to_string_lossy()
5379 .into_owned(),
5380 );
5381
5382 let allowed_inode = lookup(&fs, &temp_dir.path().join("allowed")).unwrap();
5383 let ctx = get_context();
5384
5385 let invalid_name = CString::new("..").unwrap();
5387 let res = fs.create(
5388 ctx,
5389 allowed_inode,
5390 &invalid_name,
5391 0o666,
5392 libc::O_RDWR as u32,
5393 0,
5394 None,
5395 );
5396 assert!(res.is_err());
5397 assert_eq!(res.unwrap_err().raw_os_error().unwrap(), libc::EINVAL);
5398 }
5399
5400 #[test]
5401 fn test_passthrough_fs_rename_validation() {
5402 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5403 let _guard = lock.lock().expect("acquire named lock");
5404
5405 let temp_dir = TempDir::new().unwrap();
5406 create_test_data(&temp_dir, &["allowed"], &["allowed/a.txt"]);
5407
5408 let cfg = Default::default();
5409 let fs = PassthroughFs::new("tag", cfg).unwrap();
5410 fs.init(FsOptions::empty()).unwrap();
5411
5412 let allowed_inode = lookup(&fs, &temp_dir.path().join("allowed")).unwrap();
5413 let ctx = get_context();
5414
5415 let oldname = CString::new("a.txt").unwrap();
5417 let invalid_newname = CString::new("../blocked.txt").unwrap();
5418
5419 let res = fs.rename(
5420 ctx,
5421 allowed_inode,
5422 &oldname,
5423 allowed_inode,
5424 &invalid_newname,
5425 0,
5426 );
5427 assert!(res.is_err());
5428 assert_eq!(res.unwrap_err().raw_os_error().unwrap(), libc::EINVAL);
5429 }
5430
5431 #[test]
5432 fn test_passthrough_fs_symlink_authorization() {
5433 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5434 let _guard = lock.lock().expect("acquire named lock");
5435
5436 let temp_dir = TempDir::new().unwrap();
5437 create_test_data(&temp_dir, &["allowed"], &[]);
5438
5439 let cfg = Default::default();
5440 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
5441 fs.init(FsOptions::empty()).unwrap();
5442
5443 let allowlist = Arc::new(RwLock::new(PathAllowlist::new()));
5444 fs.set_allowlist(Some(allowlist.clone()));
5445
5446 allowlist.write().unwrap().add_path(
5447 temp_dir
5448 .path()
5449 .join("allowed")
5450 .to_string_lossy()
5451 .into_owned(),
5452 );
5453
5454 let root_inode = lookup(&fs, temp_dir.path()).unwrap();
5456 let ctx = get_context();
5457
5458 let linkname = CString::new("allowed/a.txt").unwrap();
5460 let symlink_name = CString::new("malicious_link").unwrap();
5461
5462 let res = fs.symlink(ctx, &linkname, root_inode, &symlink_name, None);
5463 assert!(res.is_err());
5464 assert_eq!(res.unwrap_err().raw_os_error().unwrap(), libc::EACCES);
5465 }
5466
5467 #[test]
5468 fn test_passthrough_fs_link_authorization() {
5469 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5470 let _guard = lock.lock().expect("acquire named lock");
5471
5472 let temp_dir = TempDir::new().unwrap();
5473 create_test_data(&temp_dir, &["allowed"], &["allowed/a.txt"]);
5474
5475 let cfg = Default::default();
5476 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
5477 fs.init(FsOptions::empty()).unwrap();
5478
5479 let allowlist = Arc::new(RwLock::new(PathAllowlist::new()));
5480 fs.set_allowlist(Some(allowlist.clone()));
5481
5482 allowlist.write().unwrap().add_path(
5483 temp_dir
5484 .path()
5485 .join("allowed")
5486 .to_string_lossy()
5487 .into_owned(),
5488 );
5489
5490 let file_inode = lookup(&fs, &temp_dir.path().join("allowed/a.txt")).unwrap();
5491 let root_inode = lookup(&fs, temp_dir.path()).unwrap(); let ctx = get_context();
5493
5494 let link_name = CString::new("malicious_hardlink").unwrap();
5496 let res = fs.link(ctx, file_inode, root_inode, &link_name);
5497 assert!(res.is_err());
5498 assert_eq!(res.unwrap_err().raw_os_error().unwrap(), libc::EACCES);
5499 }
5500
5501 #[test]
5502 fn test_passthrough_fs_mknod_authorization() {
5503 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5504 let _guard = lock.lock().expect("acquire named lock");
5505
5506 let temp_dir = TempDir::new().unwrap();
5507 create_test_data(&temp_dir, &["allowed"], &[]);
5508
5509 let cfg = Default::default();
5510 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
5511 fs.init(FsOptions::empty()).unwrap();
5512
5513 let allowlist = Arc::new(RwLock::new(PathAllowlist::new()));
5514 fs.set_allowlist(Some(allowlist.clone()));
5515
5516 allowlist.write().unwrap().add_path(
5517 temp_dir
5518 .path()
5519 .join("allowed")
5520 .to_string_lossy()
5521 .into_owned(),
5522 );
5523
5524 let root_inode = lookup(&fs, temp_dir.path()).unwrap(); let ctx = get_context();
5526
5527 let name = CString::new("malicious_fifo").unwrap();
5529 let res = fs.mknod(ctx, root_inode, &name, 0o666 | libc::S_IFIFO, 0, 0, None);
5530 assert!(res.is_err());
5531 assert_eq!(res.unwrap_err().raw_os_error().unwrap(), libc::EACCES);
5532 }
5533
5534 #[test]
5535 fn test_passthrough_fs_non_utf8_bypass() {
5536 let lock = NamedLock::create(UNITTEST_LOCK_NAME).expect("create named lock");
5537 let _guard = lock.lock().expect("acquire named lock");
5538
5539 let temp_dir = TempDir::new().unwrap();
5540 let cfg = Default::default();
5541 let mut fs = PassthroughFs::new("tag", cfg).unwrap();
5542 fs.init(FsOptions::empty()).unwrap();
5543
5544 let allowlist = Arc::new(RwLock::new(PathAllowlist::new()));
5545 fs.set_allowlist(Some(allowlist.clone()));
5546
5547 let bypass_path = temp_dir
5549 .path()
5550 .join("<non UTF-8 path>")
5551 .to_string_lossy()
5552 .into_owned();
5553 allowlist.write().unwrap().add_path(bypass_path);
5554
5555 let root_inode = lookup(&fs, temp_dir.path()).unwrap();
5556 let ctx = get_context();
5557
5558 let non_utf8_name = unsafe { CString::from_vec_unchecked(vec![0xff]) };
5561 let res = fs.create(
5562 ctx,
5563 root_inode,
5564 &non_utf8_name,
5565 0o666,
5566 libc::O_RDWR as u32,
5567 0,
5568 None,
5569 );
5570
5571 assert!(res.is_err(), "Should fail because root is not writable");
5575 assert_eq!(
5576 res.unwrap_err().raw_os_error().unwrap(),
5577 libc::EILSEQ,
5578 "Should fail with EILSEQ for non-UTF8 name"
5579 );
5580 }
5581}