devices/virtio/fs/
passthrough.rs

1// Copyright 2019 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
106// RESOLVE_* constants are missing in libc crate for some targets (e.g. Android).
107// Define them here as they are stable Linux kernel API constants.
108const 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// 25 seconds is the default timeout for dbus-send.
127#[cfg(feature = "arc_quota")]
128const DEFAULT_DBUS_TIMEOUT: Duration = Duration::from_secs(25);
129
130/// Internal utility wrapper for `cros_tracing::trace_event!()` macro with VirtioFS calls.
131macro_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,       /* input/output */
170    policy: fscrypt_policy, /* output */
171}
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        // SAFETY: the length of the output slice is asserted to be within the struct it points to
178        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,     /* xflags field value (get/set) */
193    fsx_extsize: u32,    /* extsize field value (get/set) */
194    fsx_nextents: u32,   /* nextents field value (get) */
195    fsx_projid: u32,     /* project identifier (get/set) */
196    fsx_cowextsize: u32, /* CoW extsize field value (get/set) */
197    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    // __u8 digest[];
237}
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    /// Leaks the file descriptor and makes the struct unusable.
297    ///
298    /// This is an optimization to speed up dropping `OpenedFile` instances, which is useful
299    /// during an abrupt shutdown. Instead of properly closing the file descriptor, which
300    /// involves a syscall, this function effectively forgets the file descriptor, relying on the
301    /// OS to clean it up when the process terminates.
302    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, open_flags)
312    file: Mutex<OpenedFile>,
313    refcount: AtomicU64,
314    filetype: FileType,
315    path: String,
316    // This needs to be atomic because we need to set it through a shared reference.
317    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    /// If `unsafe_leak_fd` is set, this `drop` implementation will "leak" the file descriptor.
328    /// This is an optimization to speed up the cleanup process, based on the
329    /// assumption that the OS will handle the cleanup of file descriptors after the process
330    /// terminates. This is only okay if the process is guaranteed to terminate immediately
331    /// after the `PassthroughFs` instance is dropped.
332    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    /// If `unsafe_leak_fd` is set, this `drop` implementation will "leak" the file descriptor by
361    /// forgetting it. This is an optimization to speed up the cleanup process, based on the
362    /// assumption that the OS will handle the cleanup of file descriptors after the process
363    // terminates. This is only safe if the process is guaranteed to terminate immediately
364    /// after the `PassthroughFs` instance is dropped.
365    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            // Changes the effective uid/gid of the current thread to `val`. Changes the thread's
387            // credentials back to `old` when the returned struct is dropped.
388            fn new(val: $ty, old: $ty) -> io::Result<Option<$name>> {
389                if val == old {
390                    // Nothing to do since we already have the correct value.
391                    return Ok(None);
392                }
393
394                // We want credential changes to be per-thread because otherwise
395                // we might interfere with operations being carried out on other
396                // threads with different uids/gids.  However, posix requires that
397                // all threads in a process share the same credentials.  To do this
398                // libc uses signals to ensure that when one thread changes its
399                // credentials the other threads do the same thing.
400                //
401                // So instead we invoke the syscall directly in order to get around
402                // this limitation.  Another option is to use the setfsuid and
403                // setfsgid systems calls.   However since those calls have no way to
404                // return an error, it's preferable to do this instead.
405
406                // SAFETY: this call is safe because it doesn't modify any memory and we
407                // check the return value.
408                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                // SAFETY: trivially safe
420                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    // SAFETY: both calls take no parameters and only return an integer value. The kernel also
440    // guarantees that they can never fail.
441    static THREAD_EUID: libc::uid_t = unsafe { libc::syscall(SYS_GETEUID) as libc::uid_t };
442    // SAFETY: both calls take no parameters and only return an integer value. The kernel also
443    // guarantees that they can never fail.
444    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    // We have to change the gid before we change the uid because if we change the uid first then we
455    // lose the capability to change the gid.  However changing back can happen in any order.
456    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
461// Opens and returns a write-only handle to /proc/thread-self/attr/fscreate. Panics if it fails to
462// open the file.
463fn open_fscreate(proc: &File) -> File {
464    let fscreate = c"thread-self/attr/fscreate";
465
466    // SAFETY: this doesn't modify any memory and we check the return value.
467    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    // We don't expect this to fail and we're not in a position to return an error here so just
476    // panic.
477    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    // SAFETY: safe because we just opened this descriptor.
485    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            // SAFETY: this doesn't modify any memory and we check the return value.
496            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            // expect is safe here because the thread local would have been initialized by the call
516            // to `new` above.
517            let fscreate = thread_fscreate.borrow();
518            let file = fscreate
519                .as_ref()
520                .expect("Uninitialized thread-local when dropping ScopedSecurityContext");
521
522            // SAFETY: this doesn't modify any memory and we check the return value.
523            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            // SAFETY: this doesn't modify any memory and always succeeds.
544            old: unsafe { libc::umask(mask) },
545            mask,
546        }
547    }
548}
549
550impl Drop for ScopedUmask {
551    fn drop(&mut self) {
552        // SAFETY: this doesn't modify any memory and always succeeds.
553        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
578// Drops CAP_FSETID from the effective set for the current thread and returns an RAII guard that
579// adds the capability back when it is dropped.
580fn 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    // SAFETY: the kernel will only write data in `st` and we check the return value.
599    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    // SAFETY: the kernel guarantees that the struct is now fully initialized.
609    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
620/// A safe wrapper around `openat2` with a fallback to `openat64` for backward compatibility.
621///
622/// It attempts to use `openat2` to leverage secure path resolution flags (like `RESOLVE_IN_ROOT`).
623/// If `openat2` is not supported by the kernel (returns `ENOSYS`, e.g. on kernels older than 5.6),
624/// it falls back to standard path resolution using `openat64` to allow operation on older
625/// platforms.
626fn 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            // Fallback to openat64 if openat2 is not supported.
647            let fd = if let Some(m) = mode {
648                // SAFETY: openat64 doesn't modify any memory and we check the return value.
649                syscall!(unsafe {
650                    libc::openat64(dir.as_raw_descriptor(), name.as_ptr(), flags, m)
651                })
652            } else {
653                // SAFETY: openat64 doesn't modify any memory and we check the return value.
654                syscall!(unsafe { libc::openat64(dir.as_raw_descriptor(), name.as_ptr(), flags) })
655            }?;
656            // SAFETY: safe because we own the fd.
657            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    // The following constants defines the valid range of project ID used by
666    // Android and are taken from android_filesystem_config.h in Android
667    // codebase.
668    //
669    // Project IDs reserved for Android files on external storage. Total 100 IDs
670    // from PROJECT_ID_EXT_DEFAULT (1000) are reserved.
671    const PROJECT_ID_FOR_ANDROID_FILES: std::ops::RangeInclusive<u32> = 1000..=1099;
672    // Project IDs reserved for Android apps.
673    // The lower-limit of the range is PROJECT_ID_EXT_DATA_START.
674    // The upper-limit of the range differs before and after T. Here we use that
675    // of T (PROJECT_ID_APP_CACHE_END) as it is larger.
676    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
682/// Per-directory cache for `PassthroughFs::ascii_casefold_lookup()`.
683///
684/// The key of the underlying `BTreeMap` is a lower-cased file name in the direcoty.
685/// The value is the case-sensitive file name stored in the host file system.
686/// We assume that if PassthroughFs has exclusive access to the filesystem, this cache exhaustively
687///  covers all file names that exist within the directory.
688/// So every `PassthroughFs`'s handler that adds or removes files in the directory is expected to
689/// update this cache.
690struct 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
732/// Time expiring mapping from an inode of a directory to `CasefoldCache` for the directory.
733/// Each entry will be expired after `timeout`.
734/// When ascii_casefold is disabled, this struct does nothing.
735struct 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    /// Get `CasefoldCache` for the given directory.
763    /// If the cache doesn't exist, generate it by fetching directory information with
764    /// `getdents64()`.
765    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
787/// A file system that simply "passes through" all requests it receives to the underlying file
788/// system. To keep the implementation simple it servers the contents of its root directory. Users
789/// that wish to serve only a specific directory should set up the environment so that that
790/// directory ends up as the root of the file system process. One way to accomplish this is via a
791/// combination of mount namespaces and the pivot_root system call.
792///
793/// # Safety
794///
795/// The `Drop` implementation for this struct intentionally leaks all open file
796/// descriptors. It is **critical** that an instance of `PassthroughFs` is
797/// only dropped immediately prior to process termination. Failure to uphold
798/// this invariant **will** result in resource leaks. This is a deliberate
799/// performance optimization for abrupt shutdowns, where we let the OS
800/// handle resource cleanup.
801pub struct PassthroughFs {
802    // Mutex that must be acquired before executing a process-wide operation such as fchdir.
803    process_lock: Mutex<()>,
804    // virtio-fs tag that the guest uses when mounting. This is only used for debugging
805    // when tracing is enabled.
806    tag: String,
807
808    // File descriptors for various points in the file system tree.
809    inodes: Mutex<MultikeyBTreeMap<Inode, InodeAltKey, Arc<InodeData>>>,
810    next_inode: AtomicU64,
811
812    // File descriptors for open files and directories. Unlike the fds in `inodes`, these _can_ be
813    // used for reading and writing data.
814    handles: Mutex<BTreeMap<Handle, Arc<HandleData>>>,
815    next_handle: AtomicU64,
816
817    // File descriptor pointing to the `/proc` directory. This is used to convert an fd from
818    // `inodes` into one that can go into `handles`. This is accomplished by reading the
819    // `self/fd/{}` symlink. We keep an open fd here in case the file system tree that we are meant
820    // to be serving doesn't have access to `/proc`.
821    proc: File,
822
823    // Whether writeback caching is enabled for this directory. This will only be true when
824    // `cfg.writeback` is true and `init` was called with `FsOptions::WRITEBACK_CACHE`.
825    writeback: AtomicBool,
826
827    // Whether zero message opens are supported by the kernel driver.
828    zero_message_open: AtomicBool,
829
830    // Whether zero message opendir is supported by the kernel driver.
831    zero_message_opendir: AtomicBool,
832
833    // Used to communicate with other processes using D-Bus.
834    #[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    // Time-expiring cache for `ascii_casefold_lookup()`.
840    // The key is an inode of a directory, and the value is a cache for the directory.
841    // Each value will be expired `cfg.timeout` after it's created.
842    //
843    // TODO(b/267748212): Instead of per-device Mutex, we might want to have per-directory Mutex
844    // if we use PassthroughFs in multi-threaded environments.
845    expiring_casefold_lookup_caches: Option<Mutex<ExpiringCasefoldLookupCaches>>,
846
847    // paths and coresponding permission setting set by `crosvm_client_fs_permission_set` API
848    #[cfg(feature = "fs_permission_translation")]
849    permission_paths: RwLock<Vec<PermissionData>>,
850
851    // paths and coresponding xattr setting set by `crosvm_client_fs_xattr_set` API
852    #[cfg(feature = "arc_quota")]
853    xattr_paths: RwLock<Vec<XattrData>>,
854
855    cfg: Config,
856
857    // Set the root directory when pivot root isn't enabled for jailed process.
858    //
859    // virtio-fs typically uses mount namespaces and pivot_root for file system isolation,
860    // making the jailed process's root directory "/".
861    //
862    // However, Android's security model prevents crosvm from having the necessary SYS_ADMIN
863    // capability for mount namespaces and pivot_root. This lack of isolation means that
864    // root_dir defaults to the path provided via "--shared-dir".
865    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        // SAFETY: this doesn't modify any memory and we check the return value.
887        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        // Privileged UIDs can use D-Bus to perform some operations.
896        #[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        // SAFETY: safe because we just opened this descriptor.
912        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    /// Validates and resolves a write path component against the dynamic path allowlist.
973    ///
974    /// This function enforces security boundaries by performing the following checks:
975    /// 1. Checks if the `name` component does not contain malicious patterns (e.g., `..` or `/`).
976    /// 2. Converts the `name` from a `CStr` to a UTF-8 string slice, failing with `EILSEQ` if
977    ///    invalid.
978    /// 3. Checks if the allowlist authorizes write access for the resolved path.
979    ///
980    /// # Errors
981    ///
982    /// Returns an `io::Error` if any validation or authorization check fails:
983    /// * `EINVAL` - If `name` contains `..` or `/`.
984    /// * `EILSEQ` - If `name` is not valid UTF-8.
985    /// * `EACCES` - If the dynamic allowlist does not grant full write access to the resolved path.
986    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        // Does not include nul-terminator.
1052        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        // The unwrap is safe here because the prefix doesn't contain any interior nul-bytes and the
1061        // to_bytes() call above will not return a byte slice with any interior nul-bytes either.
1062        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        // SAFETY: this doesn't modify any memory and we check the return value. We don't really
1083        // check `flags` because if the kernel can't handle poorly specified flags then we have
1084        // much bigger problems. Also, clear the `O_NOFOLLOW` flag if it is set since we need
1085        // to follow the `/proc/self/fd` symlink to get the file.
1086        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        // SAFETY: safe because we just opened this descriptor.
1095        Ok(unsafe { File::from_raw_descriptor(raw_descriptor) })
1096    }
1097
1098    /// Modifies the provided open flags based on the writeback caching configuration.
1099    /// Return the updated open flags.
1100    fn update_open_flags(&self, mut flags: i32) -> i32 {
1101        // When writeback caching is enabled, the kernel may send read requests even if the
1102        // userspace program opened the file write-only. So we need to ensure that we have opened
1103        // the file for reading as well as writing.
1104        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        // When writeback caching is enabled the kernel is responsible for handling `O_APPEND`.
1111        // However, this breaks atomicity as the file may have changed on disk, invalidating the
1112        // cached copy of the data in the kernel and the offset that the kernel thinks is the end of
1113        // the file. Just allow this for now as it is the user's responsibility to enable writeback
1114        // caching only for directories that are not shared. It also means that we need to clear the
1115        // `O_APPEND` flag.
1116        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        // handle writeback caching cases
1125        flags = self.update_open_flags(flags);
1126
1127        self.open_fd(inode.as_raw_descriptor(), flags)
1128    }
1129
1130    // Increases the inode refcount and returns the inode.
1131    fn increase_inode_refcount(&self, inode_data: &InodeData) -> Inode {
1132        // Matches with the release store in `forget`.
1133        inode_data.refcount.fetch_add(1, Ordering::Acquire);
1134        inode_data.inode
1135    }
1136
1137    // Creates a new entry for `f` or increases the refcount of the existing entry for `f`.
1138    // The inodes mutex lock must not be already taken by the same thread otherwise this
1139    // will deadlock.
1140    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            // We use the same timeout for the attribute and the entry.
1184            attr_timeout: self.cfg.timeout,
1185            entry_timeout: self.cfg.timeout,
1186        }
1187    }
1188
1189    /// Acquires lock of `expiring_casefold_lookup_caches` if `ascii_casefold` is enabled.
1190    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    // Returns an actual case-sensitive file name that matches with the given `name`.
1197    // Returns `Ok(None)` if no file matches with the give `name`.
1198    // This function will panic if casefold is not enabled.
1199    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    // Performs an ascii case insensitive lookup.
1212    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        // Check if we already have an entry before opening a new file.
1251        if let Some(data) = self.inodes.lock().get_alt(&altkey) {
1252            // Return the same inode with the reference counter increased.
1253            #[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                // We use the same timeout for the attribute and the entry.
1262                attr_timeout: self.cfg.timeout,
1263                entry_timeout: self.cfg.timeout,
1264            });
1265        }
1266
1267        // Now we need to get a file descriptor that can be used for operations
1268        // that don't support O_PATH. We try to open it with O_RDONLY or O_DIRECTORY
1269        // first.
1270        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        // We use /proc/self/fd/{path_fd} to open the file again with full permissions.
1278        // This is safe because we resolved the path securely above.
1279        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        // SAFETY: this doesn't modify any memory and we check the return value.
1283        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                // Fall back to O_PATH if we can't read it.
1289                flags |= libc::O_PATH;
1290                // SAFETY: this doesn't modify any memory and we check the return value.
1291                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        // SAFETY: safe because we own the fd.
1299        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            // We only set the direct I/O option on files.
1308            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    // Performs lookup using original name first, if it fails and ascii_casefold is enabled,
1325    // it tries to unfold the name and do lookup again.
1326    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 `ascii_casefold` is enabled, fallback to `ascii_casefold_lookup()`.
1333        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                // We don't need to close the file here because that will happen automatically when
1365                // the last `Arc` is dropped.
1366                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        // SAFETY: this doesn't modify any memory and we check the return value.
1390        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        // SAFETY: this doesn't modify any memory and we check the return value.
1396        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    // Changes the CWD to `self.proc`, runs `f`, and then changes the CWD back to the root
1408    // directory. This effectively emulates an *at syscall starting at /proc, which is useful when
1409    // there is no *at syscall available. Panics if any of the fchdir calls fail or if there is no
1410    // root inode.
1411    //
1412    // NOTE: this method acquires an `self`-wide lock. If any locks are acquired in `f`, care must
1413    // be taken to avoid the risk of deadlocks.
1414    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        // Acquire a lock for `fchdir`.
1421        let _proc_lock = self.process_lock.lock();
1422        // SAFETY: this doesn't modify any memory and we check the return value. Since the
1423        // fchdir should never fail we just use debug_asserts.
1424        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        // SAFETY: this doesn't modify any memory and we check the return value. Since the
1435        // fchdir should never fail we just use debug_asserts.
1436        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            // For FDs opened with `O_PATH`, we cannot call `fgetxattr` normally. Instead we
1452            // emulate an _at syscall by changing the CWD to /proc, running the path based syscall,
1453            //  and then setting the CWD back to the root directory.
1454            let path = CString::new(format!("self/fd/{}", file.as_raw_descriptor()))
1455                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1456
1457            // SAFETY: this will only modify `value` and we check the return value.
1458            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            // For regular files and directories, we can just use fgetxattr.
1468            // SAFETY: this will only write to `value` and we check the return value.
1469            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        // SAFETY: this struct only has integer fields and any value is valid.
1499        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            // SAFETY: the kernel will only write to `arg` and we check the return value.
1507            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        // SAFETY: the kernel will only write to `buf` and we check the return value.
1526        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            // SAFETY: the kernel guarantees that the policy is now initialized.
1531            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        // Changing quota project ID requires CAP_FOWNER or being file owner.
1559        // Here we use privileged_quota_uids because we cannot perform a CAP_FOWNER check.
1560        #[cfg(feature = "arc_quota")]
1561        if ctx_uid == st.st_uid || self.cfg.privileged_quota_uids.contains(&ctx_uid) {
1562            // Get the current fsxattr.
1563            let mut buf = MaybeUninit::<fsxattr>::zeroed();
1564            // SAFETY: the kernel will only write to `buf` and we check the return value.
1565            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            // SAFETY: the kernel guarantees that the policy is now initialized.
1570            let current_attr = unsafe { buf.assume_init() };
1571
1572            // Project ID cannot be changed inside a user namespace.
1573            // Use Spaced to avoid this restriction.
1574            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        //  SAFETY: this doesn't modify any memory and we check the return value.
1604        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        // The ioctl encoding is a long but the parameter is actually an int.
1620        let mut flags: c_int = 0;
1621
1622        // SAFETY: the kernel will only write to `flags` and we check the return value.
1623        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        // The ioctl encoding is a long but the parameter is actually an int.
1645        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        // Only privleged uid can perform FS_IOC_SETFLAGS through cryptohome.
1655        #[cfg(feature = "arc_quota")]
1656        if ctx_uid == st.st_uid || self.cfg.privileged_quota_uids.contains(&ctx_uid) {
1657            // Get the current flag.
1658            let mut buf = MaybeUninit::<c_int>::zeroed();
1659            // SAFETY: the kernel will only write to `buf` and we check the return value.
1660            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            // SAFETY: the kernel guarantees that the policy is now initialized.
1665            let current_flags = unsafe { buf.assume_init() };
1666
1667            // Project inheritance flag cannot be changed inside a user namespace.
1668            // Use Spaced to avoid this restriction.
1669            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                // If the input flags contain FS_PROJINHERIT_FL, then it is a set. Otherwise it is a
1677                // reset.
1678                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        // SAFETY: this doesn't modify any memory and we check the return value.
1698        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        // These match the return codes from `fsverity_ioctl_enable` in the kernel.
1715        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            // We cannot enable verity while holding a writable fd so get a new one, if necessary.
1723            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                    // We need to get a read-only handle for this file.
1731                    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                // We can't enable verity while holding a writable fd. We don't know whether the
1746                // file was opened for writing so check it here. We don't expect
1747                // this to be a frequent operation so the extra latency should be
1748                // fine.
1749                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                        // We need to get a read-only handle for this file.
1754                        *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        // SAFETY: this doesn't modify any memory and we check the return value.
1798        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        // Taken from fs/verity/fsverity_private.h.
1823        const FS_VERITY_MAX_DIGEST_SIZE: u16 = 64;
1824
1825        // This digest size is what the fsverity command line utility uses.
1826        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        // Make sure we get a properly aligned allocation.
1831        let mut buf = [MaybeUninit::<fsverity_digest>::uninit(); ROUNDED_LEN];
1832
1833        // SAFETY: we are only writing data and not reading uninitialized memory.
1834        unsafe {
1835            // TODO: Replace with `MaybeUninit::slice_as_mut_ptr` once it is stabilized.
1836            addr_of_mut!((*(buf.as_mut_ptr() as *mut fsverity_digest)).digest_size)
1837                .write(DIGEST_SIZE)
1838        };
1839
1840        // SAFETY: this will only modify `buf` and we check the return value.
1841        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                // SAFETY: this value was initialized by us already and then overwritten by the kernel.
1847                // TODO: Replace with `MaybeUninit::slice_as_ptr` once it is stabilized.
1848                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            // The kernel guarantees this but it doesn't hurt to be paranoid.
1852            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                // SAFETY: any bit pattern is valid for `MaybeUninit<u8>` and `fsverity_digest`
1861                // doesn't contain any references.
1862                unsafe { mem::transmute(buf) };
1863
1864            let buf =
1865                // SAFETY: Casting to `*const [u8]` is safe because the kernel guarantees that the
1866                // first `outlen` bytes of `buf` are initialized and `MaybeUninit<u8>` is guaranteed
1867                // to have the same layout as `u8`.
1868                // TODO: Replace with `MaybeUninit::slice_assume_init_ref` once it is stabilized.
1869                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    /// Set permission according to path
1908    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    /// Set host uid/gid to configured value according to path
1927    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    /// Set host uid/gid to configured value according to path
1938    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    /// Convert u8 slice to string
1980    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    /// Set permission according to path
1991    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    /// Set host uid/gid to configured value according to path
2007    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    /// Set host uid/gid to configured value according to path
2018    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    /// Sets uid/gid/umask for all files and directories under a specific path.
2053    ///
2054    /// This ioctl does not correspond to any upstream FUSE feature. It is used for arcvm
2055    /// It associates the specified path with the provide uid, gid, and umask values within the
2056    /// filesystem metadata.
2057    ///
2058    /// During subsequent lookup operations, the stored uid/gid/umask values are retrieved and
2059    /// applied to all files and directories found under the registered path. Before sending
2060    /// file stat information to the client, the uid and gid are substituted by `guest_uid` and
2061    /// `guest_gid` if the file falls under the registered path. The file mode is masked by the
2062    ///  umask.
2063    ///
2064    /// When the guest creates a file within the specified path, the file gid/uid stat in host
2065    /// will be overwritten to `host_uid` and `host_gid` values.
2066    ///
2067    /// This functionality enables dynamic configuration of ownership and permissions for a
2068    /// specific directory hierarchy within the filesystem.
2069    ///
2070    /// # Notes
2071    /// - This method affects all existing and future files under the registered path.
2072    /// - The original file ownership and permissions are overridden by the provided values.
2073    /// - The registered path should not be renamed
2074    /// - Refer go/remove-mount-passthrough-fuse for more design details
2075    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    // Get xattr value according to path and name
2107    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    /// Sets xattr value for all files and directories under a specific path.
2140    ///
2141    /// This ioctl does not correspond to any upstream FUSE feature. It is used for arcvm.
2142    /// It associates the specified path and xattr name with a value.
2143    ///
2144    /// When the getxattr is called for the specified path and name, the predefined
2145    /// value is returned.
2146    ///
2147    /// # Notes
2148    /// - This method affects all existing and future files under the registered path.
2149    /// - The SECURITY_CONTEXT feature will be disabled if this ioctl is enabled.
2150    /// - The registered path should not be renamed
2151    /// - Refer go/remove-mount-passthrough-fuse for more design details
2152    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    /// Looks up the host uid according to the path of file that inode is referring to.
2204    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
2222/// Decrements the refcount of the inode.
2223/// Returns `true` if the refcount became 0.
2224fn 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        // Acquiring the write lock on the inode map prevents new lookups from incrementing the
2231        // refcount but there is the possibility that a previous lookup already acquired a
2232        // reference to the inode data and is in the process of updating the refcount so we need
2233        // to loop here until we can decrement successfully.
2234        loop {
2235            let refcount = data.refcount.load(Ordering::Relaxed);
2236
2237            // Saturating sub because it doesn't make sense for a refcount to go below zero and
2238            // we don't want misbehaving clients to cause integer overflow.
2239            let new_count = refcount.saturating_sub(count);
2240
2241            // Synchronizes with the acquire load in `do_lookup`.
2242            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                    // We just removed the last refcount for this inode. There's no need for an
2249                    // acquire fence here because we hold a write lock on the inode map and any
2250                    // thread that is waiting to do a forget on the same inode will have to wait
2251                    // until we release the lock. So there's is no other release store for us to
2252                    // synchronize with before deleting the entry.
2253                    inodes.remove(&inode);
2254                    return true;
2255                }
2256                break;
2257            }
2258        }
2259    }
2260    false
2261}
2262
2263// Strips any `user.virtiofs.` prefix from `buf`. If buf contains one or more nul-bytes, each
2264// nul-byte-separated slice is treated as a C string and the prefix is stripped from each one.
2265fn 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    /// The `Drop` implementation for this struct intentionally leaks all open file descriptors.
2295    /// It sets the `unsafe_leak_fd` flag on all `InodeData` and `HandleData` instances, which
2296    /// causes their `drop` implementations to forget the underlying `File` objects.
2297    ///
2298    /// This is a deliberate performance optimization for abrupt shutdowns. It relies on the
2299    /// operating system to clean up the file descriptors when the process terminates. It is
2300    /// **critical** that an instance of `PassthroughFs` is only dropped immediately prior to
2301    /// process termination.
2302    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        // SAFETY: this doesn't modify any memory and we check the return value.
2323        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        // SAFETY: safe because we just opened this descriptor above.
2329        let f = unsafe { File::from_raw_descriptor(raw_descriptor) };
2330
2331        let st = stat(&f)?;
2332
2333        // SAFETY: this doesn't modify any memory and there is no need to check the return
2334        // value because this system call always succeeds. We need to clear the umask here because
2335        // we want the client to be able to set all the bits in the mode.
2336        unsafe { libc::umask(0o000) };
2337
2338        let mut inodes = self.inodes.lock();
2339
2340        // Not sure why the root inode gets a refcount of 2 but that's what libfuse does.
2341        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        // Device using dynamic xattr feature will have different security context in
2364        // host and guests. The SECURITY_CONTEXT feature should not be enabled in the
2365        // device.
2366        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        // SAFETY: this will only modify `out` and we check the return value.
2403        syscall!(unsafe { libc::fstatvfs64(data.as_raw_descriptor(), out.as_mut_ptr()) })?;
2404
2405        // SAFETY: the kernel guarantees that `out` has been initialized.
2406        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        // FUSE takes a inode=0 as a request to do negative dentry cache.
2427        // So, if `negative_timeout` is set, return success with the timeout value and inode=0 as a
2428        // response.
2429        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            // SAFETY: this doesn't modify any memory and we check the return value.
2522            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        // TODO(b/278691962): If ascii_casefold is enabled, we need to call
2536        // `get_case_unfolded_name()` to get the actual name to be unlinked.
2537        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        // Identify the absolute path of the directory being read.
2556        // This path is required to construct full paths for each directory entry
2557        // during filtering.
2558        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 an allowlist is configured, inject the allowlist context into the ReadDir
2574        // iterator to enable directory entry filtering (hiding unauthorized files).
2575        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            // SAFETY: this doesn't modify any memory and we check the return value.
2661            syscall!(unsafe {
2662                libc::openat64(
2663                    data.as_raw_descriptor(),
2664                    current_dir.as_ptr(),
2665                    tmpflags,
2666                    mode,
2667                )
2668            })?
2669        };
2670        // No need to add casefold_cache becuase we created an anonymous file.
2671
2672        // SAFETY: safe because we just opened this fd.
2673        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        // Mask out O_DIRECT. Also mask out O_PATH because we need to return a readable/writable
2723        // file descriptor for create.
2724        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                // Don't leak the entry.
2756                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        // TODO(b/278691962): If ascii_casefold is enabled, we need to call
2768        // `get_case_unfolded_name()` to get the actual name to be unlinked.
2769        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                    // We need to get a readable handle for this file.
2799                    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        // When the WRITE_KILL_PRIV flag is set, drop CAP_FSETID so that the kernel will
2829        // automatically clear the setuid and setgid bits for us.
2830        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                    // We need to get a writable handle for this file.
2856                    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        // If we have a handle then use it otherwise get a new fd from the inode.
2903        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            // SAFETY: this doesn't modify any memory and we check the return value.
2915            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                // Cannot use -1 here because these are unsigned values.
2930                u32::MAX
2931            };
2932            let gid = if valid.contains(SetattrValid::GID) {
2933                attr.st_gid
2934            } else {
2935                // Cannot use -1 here because these are unsigned values.
2936                u32::MAX
2937            };
2938
2939            // SAFETY: this doesn't modify any memory and we check the return value.
2940            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                    // SAFETY: this doesn't modify any memory and we check the return value.
2955                    unsafe { libc::ftruncate64(fd.as_raw_descriptor(), attr.st_size) }
2956                }
2957                _ => {
2958                    // There is no `ftruncateat` so we need to get a new fd and truncate it.
2959                    let f = self.open_inode(&inode_data, libc::O_NONBLOCK | libc::O_RDWR)?;
2960                    // SAFETY: this doesn't modify any memory and we check the return value.
2961                    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            // SAFETY: this doesn't modify any memory and we check the return value.
2993            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        // Both old and new names must be writable.
3019        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            // SAFETY: this doesn't modify any memory and we check the return value.
3025            // TODO: Switch to libc::renameat2 once https://github.com/rust-lang/libc/pull/1508 lands
3026            // and we have glibc 2.28.
3027            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            // SAFETY: this doesn't modify any memory and we check the return value.
3088            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            // SAFETY: this doesn't modify any memory and we check the return value.
3122            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            // SAFETY: this doesn't modify any memory and we check the return value.
3168            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        // SAFETY: this will only modify the contents of `buf` and we check the return value.
3186        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        // SAFETY:
3225        // Since this method is called whenever an fd is closed in the client, we can emulate that
3226        // behavior by doing the same thing (dup-ing the fd and then immediately closing it). Safe
3227        // because this doesn't modify any memory and we check the return values.
3228        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            // The file exists since we were able to call `stat(2)` on it.
3283            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        // root can only execute something if it is executable by one of the owner, the group, or
3307        // everyone.
3308        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        // We can't allow the VM to set this xattr because an unprivileged process may use it to set
3331        // a privileged xattr.
3332        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            // For FDs opened with `O_PATH`, we cannot call `fsetxattr` normally. Instead we emulate
3353            // an _at syscall by changing the CWD to /proc, running the path based syscall, and then
3354            // setting the CWD back to the root directory.
3355            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                // SAFETY: this doesn't modify any memory and we check the return value.
3360                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                // For regular files and directories, we can just use fsetxattr.
3373                // SAFETY: this doesn't modify any memory and we check the return value.
3374                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        // We don't allow the VM to set this xattr so we also pretend there is no value associated
3398        // with it.
3399        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            // For FDs opened with `O_PATH`, we cannot call `flistxattr` normally. Instead we
3431            // emulate an _at syscall by changing the CWD to /proc, running the path based syscall,
3432            // and then setting the CWD back to the root directory.
3433            let path = CString::new(format!("self/fd/{}", file.as_raw_descriptor()))
3434                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
3435
3436            // SAFETY: this will only modify `buf` and we check the return value.
3437            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            // For regular files and directories, we can just flistxattr.
3446            // SAFETY: this will only write to `buf` and we check the return value.
3447            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        // We don't allow the VM to set this xattr so we also pretend there is no value associated
3471        // with it.
3472        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            // For files opened with `O_PATH`, we cannot call `fremovexattr` normally. Instead we
3483            // emulate an _at syscall by changing the CWD to /proc, running the path based syscall,
3484            // and then setting the CWD back to the root directory.
3485            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                    // SAFETY: this doesn't modify any memory and we check the return value.
3490                    unsafe { libc::removexattr(path.as_ptr(), name.as_ptr()) }))?;
3491        } else {
3492            // For regular files and directories, we can just use fremovexattr.
3493            syscall!(
3494                // SAFETY: this doesn't modify any memory and we check the return value.
3495                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                // fallocate needs a writable fd
3518                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                        // We need to get a writable handle for this file.
3526                        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        // SAFETY: this doesn't modify any memory and we check the return value.
3541        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            // The following is ARCVM-specific ioctl
3615            // Refer go/remove-mount-passthrough-fuse for more design details
3616            #[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        // We need to change credentials during a write so that the kernel will remove setuid or
3671        // setgid bits from the file if it was written to by someone other than the owner.
3672        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            // SAFETY: this call is safe because it doesn't modify any memory and we
3688            // check the return value.
3689            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            // Write-only is mapped to O_RDWR since mmap always requires an fd opened for reading.
3733            (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                    // We have a read-only or write-only fd and we need to upgrade it.
3747                    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        // Perform lookup but not create negative dentry
3799        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        // This lookup serves two purposes:
3812        // 1. If the O_CREATE flag is not set, it retrieves the d_entry for the file.
3813        // 2. If the O_CREATE flag is set, it checks whether the file exists.
3814        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                // If the file did not exist & O_CREAT is set,
3819                // create file & set FILE_CREATED bits in open options
3820                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        // SAFETY: checked res is not error before
3837        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        // 1. Verify Lookups of allowed paths succeed
3903        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        // 2. Verify Lookups of blocked paths fail with NotFound (ENOENT)
3907        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        // 3. Verify Write/Creation of allowed paths succeed
3916        assert!(create(&fs, &temp_dir.path().join("allowed/new_file.txt")).is_ok());
3917
3918        // 4. Verify Write/Creation inside a blocked directory fails with NotFound (because the
3919        //    parent directory cannot be resolved/looked up)
3920        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        // 5. Verify Write/Creation directly in the ancestor directory (root/temp_dir) fails with
3925        //    PermissionDenied (EACCES)
3926        // (the parent directory lookup succeeds because ancestors are allowed, but the write itself
3927        // is blocked)
3928        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        // Write some data to the file
3942        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        // 1. Lookup & Open allowed file to get Inode and Handle
3962        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        // 2. Remove path from allowlist
3970        allowlist.write().unwrap().remove_path(&allowed_path);
3971
3972        // 3. Verify that reading from the already open handle still succeeds
3973        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        // 4. Verify that new lookup of the file now fails
4013        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    // Create an instance of `Context` with valid uid, gid, and pid.
4019    // The correct ids are necessary for test cases where new files are created.
4020    fn get_context() -> Context {
4021        // SAFETY: both calls take no parameters and only return an integer value. The kernel also
4022        // guarantees that they can never fail.
4023        let uid = unsafe { libc::syscall(SYS_GETEUID) as libc::uid_t };
4024        // SAFETY: both calls take no parameters and only return an integer value. The kernel also
4025        // guarantees that they can never fail.
4026        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    /// Creates the given directories and files under `temp_dir`.
4032    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    /// Looks up the given `path` in `fs`.
4045    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    /// Looks up the given `path` in `fs`.
4062    #[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    /// Creates a file at the given `path`.
4081    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    /// Removes a file at the given `path`.
4100    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    /// Forgets cache.
4109    fn forget(fs: &PassthroughFs, path: &Path) -> io::Result<()> {
4110        let ctx = get_context();
4111        let inode = lookup(fs, path)?;
4112        // Pass `u64::MAX` to ensure that the refcount goes to 0 and we forget inode.
4113        fs.forget(ctx, inode, u64::MAX);
4114        Ok(())
4115    }
4116
4117    /// Looks up and open the given `path` in `fs`.
4118    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        // Do lookup before util (vec_len-1)-th pathname, this operation is to simulate
4133        // the behavior of VFS, since when VFS call atomic_open only at last look up.
4134        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    // In this ioctl inode,handle,flags,arg and out_size is irrelavant, set to empty value.
4160    #[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    // In this ioctl inode,handle,flags,arg and out_size is irrelavant, set to empty value.
4181    #[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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4204        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4205        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        // Selinux shouldn't get overwritten.
4216        let selinux = c"security.selinux";
4217        assert_eq!(p.rewrite_xattr_name(selinux).to_bytes(), selinux.to_bytes());
4218
4219        // user, trusted, and system should not be changed either.
4220        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        // sehash should be re-written.
4228        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4277        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4278        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        // "A.txt" is different from "a.txt".
4301        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4312        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4313        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        // Ensure that "A.txt" is equated with "a.txt".
4329        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4358        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4359        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        // Create a.txt and b.txt.
4376        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        // Remove a.txt only
4392        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        // "A.TXT" must not be found regardless of whether casefold is enabled or not.
4401        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        // Check if the host file system doesn't have a.txt but does b.txt.
4411        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 /* casefold */);
4418    }
4419
4420    #[test]
4421    fn create_and_remove_casefold() {
4422        test_create_and_remove(true /* casefold */);
4423    }
4424
4425    fn test_create_and_forget(ascii_casefold: bool) {
4426        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4427        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4428        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        // Create a.txt.
4445        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 a.txt's inode from PassthroughFs's internal cache.
4454        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 /* ascii_casefold */);
4480    }
4481
4482    #[test]
4483    fn create_and_forget_casefold() {
4484        test_create_and_forget(true /* ascii_casefold */);
4485    }
4486
4487    #[test]
4488    fn casefold_lookup_cache() {
4489        let temp_dir = TempDir::new().unwrap();
4490        // Prepare `a.txt` before starting the test.
4491        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        // Since `a.txt` exists, "A.TXT" must exist.
4505        let large_a_path = temp_dir.path().join("A.TXT");
4506        // Looking up "A.TXT" must create a CasefoldCache entry.
4507        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        // Create b.txt.
4511        let b_path = temp_dir.path().join("b.txt");
4512        create(&fs, &b_path).expect("create b.txt");
4513        // Then, b.txt must exists in the cache.
4514        assert!(fs.exists_in_casefold_cache(parent, &CString::new("B.TXT").unwrap()));
4515        // When removing b.txt, it must be removed from the cache as well.
4516        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        // Prepare `a.txt` before starting the test.
4524        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        // a.txt hasn't existed yet.
4537        // Since negative_timeout is enabled, success with inode=0 is expected.
4538        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        // Create a.txt
4544        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        // Remove a.txt
4551        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4570        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4571        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        // atomic_open with flag O_RDWR, should return positive dentry and file handler
4592        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        // atomic_open with flag O_RDWR |  O_CREATE, should return positive dentry and file handler
4616        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        // atomic_open with flag O_RDWR | O_CREATE | O_EXCL, should return positive dentry and file
4640        // handler
4641        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4666        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4667        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        // atomic_open with flag O_RDWR, should return NO_EXIST error
4687        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        // atomic_open with flag O_RDWR | O_CREATE, should return positive dentry and file handler
4700        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4730        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4731        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        // atomic open the link destination file
4744        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        // create depth 1 symbol link
4758        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        // atomic_open symbol link, should return dentry with no handler
4769        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        // delete link destination
4784        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        // after link destination removed, should still return valid dentry
4794        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4813        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4814        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        // Check expected data item is added to permission_paths.
4856        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        // Second ioctl should not succeed since max_dynamic_perm is set to 1
4867        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
4884        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
4885        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        // The perm_path is not valid since it does not start with /.
4895        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        // In this ioctl inode,handle,flags,arg and out_size is irrelavant, set to empty value.
4912        // This call is supposed to get EINVAL ioctlReply, since the perm_path is invalid.
4913        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        // This call is supposed to get EINVAL ioctlReply, since the in_size is not the size of
4925        // struct FsPermissionDataBuffer.
4926        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        // Prepare `a.txt` before starting the test.
4939        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        // a_path is the path with out set permission by path
4969        let a_path = temp_dir.path().join("a.txt");
4970        let in_dir_a_path = dir_path.join("a.txt");
4971
4972        // a.txt should not be set with guest_uid/guest_uid/umask by path
4973        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        // a.txt in dir should be set guest_uid/guest_uid/umask by path
4978        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        // Create dir/b.txt.
4984        let in_dir_b_path = dir_path.join("b.txt");
4985        create(&fs, &in_dir_b_path).expect("create b.txt");
4986
4987        // newly created b.txt in dir should be set guest_uid/guest_uid/umask by path
4988        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        // Use a fake UID in the context that would normally fail set_creds()
5012        let mut fake_ctx = real_ctx;
5013        fake_ctx.uid = 9999;
5014        fake_ctx.gid = 9999;
5015
5016        // Create mapping: mapping the fake guest UID to the REAL host UID.
5017        // If the mapping works, copy_file_range will use real_ctx.uid and succeed.
5018        // If the mapping is ignored, it will use fake_ctx.uid (9999) and set_creds will fail with
5019        // EPERM.
5020        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        // Open files to get handles.
5039        // Note: we use real_ctx here to ensure file handles are opened successfully.
5040        // The copy_file_range call itself will use fake_ctx.
5041        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        // Execute copy_file_range with fake_ctx.
5052        // This will only succeed if change_creds_for_path correctly translates 9999 -> real_uid.
5053        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
5072        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
5073        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        // Check expected data item is added to permission_paths.
5119        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        // Second ioctl should not succeed since max_dynamic_perm is set to 1
5127        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        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
5143        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
5144        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        // This call is supposed to get EINVAL ioctlReply, since the perm_path is invalid.
5179        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        // This call is supposed to get EINVAL ioctlReply, since the in_size is not the size of
5194        // struct FsPathXattrDataBuffer.
5195        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        // Prepare `a.txt` before starting the test.
5208        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        // a_path is the path with out set xattr by path
5236        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        // a.txt should not be set with xattr by path
5241        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        // a.txt in dir should be set xattr by path
5252        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        // Create dir/b.txt.
5262        let in_dir_b_path = dir_path.join("b.txt");
5263        create(&fs, &in_dir_b_path).expect("create b.txt");
5264
5265        // newly created b.txt in dir should be set xattr by path
5266        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    /// Creates and open a new file by atomic_open with O_APPEND flag.
5279    /// We check O_APPEND is properly handled, depending on writeback cache is enabled or not.
5280    fn atomic_open_create_o_append(writeback: bool) {
5281        // Since PassthroughFs may executes process-wide operations such as `fchdir`, acquire
5282        // `NamedLock` before starting each unit test creating a `PassthroughFs` instance.
5283        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            // When writeback is enabled, O_APPEND must be handled by the guest kernel.
5314            // So, it must be cleared.
5315            assert_eq!(flags & libc::O_APPEND, 0);
5316        } else {
5317            // Without writeback cache, O_APPEND must not be cleared.
5318            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        // Create a secret file in the parent of root
5341        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        // 1. Lookup ".." from root (inode 1)
5354        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        // Creation with a name containing '..' should fail with EINVAL
5386        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        // Rename with '..' in newname should fail with EINVAL
5416        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        // Get Inode of the shared root (which is an ancestor, hence not writable)
5455        let root_inode = lookup(&fs, temp_dir.path()).unwrap();
5456        let ctx = get_context();
5457
5458        // Symlink creation in the shared root (not writable) should fail with EACCES
5459        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(); // Directory not writable
5492        let ctx = get_context();
5493
5494        // Hardlink creation in a non-writable directory should fail with EACCES
5495        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(); // Directory not writable
5525        let ctx = get_context();
5526
5527        // mknod creation in a non-writable directory should fail with EACCES
5528        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        // Register the fallback string path in allowlist
5548        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        // SAFETY: The vector [0xff] does not contain any interior null bytes, which satisfies
5559        // CString's safety invariant.
5560        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        // We expect it to be blocked by allowlist (EACCES) because the root directory itself is not
5572        // writable. However, due to the unwrap_or("<non UTF-8 path>") bug, it will match
5573        // the registered bypass path and bypass the allowlist check.
5574        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}