Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 75 additions & 8 deletions platform/sandbox/src/workspace/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,23 @@ mod linux;
fn basis(directory: &Dir) -> io::Result<impl Serialize> {
let metadata = directory.dir_metadata()?;
#[cfg(target_os = "macos")]
let volume = volume_uuid(directory)?;
let (volume, stable_birth) = (volume_uuid(directory)?, true);
#[cfg(target_os = "linux")]
let volume = linux::volume(directory)?;
let linux::Volume {
id: volume,
stable_birth,
} = linux::volume(directory)?;
#[cfg(windows)]
let volume = metadata.dev(); // cap-fs-ext returns the filesystem's volume serial number.
let birth = metadata
.created()
.ok()
.and_then(|time| time.into_std().duration_since(UNIX_EPOCH).ok())
.map(|duration| (duration.as_secs(), duration.subsec_nanos()));
let (volume, stable_birth) = (metadata.dev(), true); // cap-fs-ext returns the filesystem's volume serial number.
let birth = if stable_birth {
metadata
.created()
.ok()
.and_then(|time| time.into_std().duration_since(UNIX_EPOCH).ok())
.map(|duration| (duration.as_secs(), duration.subsec_nanos()))
} else {
None
};
Ok((volume, metadata.ino(), birth))
}

Expand Down Expand Up @@ -117,10 +124,70 @@ mod tests {
std::fs::create_dir(&path).unwrap();
let directory = Dir::open_ambient_dir(&path, cap_std::ambient_authority()).unwrap();
let grant = DirectoryIdentity::capture(&directory).unwrap();

let probe = path.join(".probe");
std::fs::write(&probe, b"ok").unwrap();
std::fs::remove_file(&probe).unwrap();
let same_reopened = Dir::open_ambient_dir(&path, cap_std::ambient_authority()).unwrap();
assert!(grant.matches(&same_reopened).unwrap());

std::fs::rename(&path, temp.path().join("original")).unwrap();
std::fs::create_dir(&path).unwrap();
let replacement = Dir::open_ambient_dir(&path, cap_std::ambient_authority()).unwrap();
assert!(!grant.matches(&replacement).unwrap());
assert!(grant.matches(&directory).unwrap());
}

#[cfg(target_os = "linux")]
#[test]
#[ignore = "requires a fresh lower-layer overlayfs directory in MAKA_TEST_OVERLAYFS_DIR"]
fn captured_grant_survives_linux_overlayfs_copy_up() {
let (path, directory) = overlayfs_directory();

let before_birth = directory.dir_metadata().unwrap().created().ok();
let grant = DirectoryIdentity::capture(&directory).unwrap();

let probe = tempfile::NamedTempFile::new_in(&path).unwrap();
drop(probe);

let after_dir = Dir::open_ambient_dir(&path, cap_std::ambient_authority()).unwrap();
let after_birth = after_dir.dir_metadata().unwrap().created().ok();
assert_ne!(
before_birth, after_birth,
"expected overlayfs copy-up to update directory birth time"
);
assert!(grant.matches(&after_dir).unwrap());
}

#[cfg(target_os = "linux")]
#[test]
#[ignore = "requires an overlayfs uuid=off/null mount in MAKA_TEST_OVERLAYFS_DIR"]
fn captured_grant_rejects_linux_overlayfs_without_uuid() {
let (_, directory) = overlayfs_directory();
assert_eq!(
DirectoryIdentity::capture(&directory).unwrap_err().kind(),
io::ErrorKind::Unsupported
);
}

#[cfg(target_os = "linux")]
fn overlayfs_directory() -> (std::path::PathBuf, Dir) {
use std::os::fd::AsRawFd;

let path = std::path::PathBuf::from(
std::env::var_os("MAKA_TEST_OVERLAYFS_DIR").expect("overlayfs fixture directory"),
);
let directory = Dir::open_ambient_dir(&path, cap_std::ambient_authority()).unwrap();
let mut statfs = std::mem::MaybeUninit::<libc::statfs>::uninit();
// SAFETY: fstatfs initializes the struct on success, and directory holds the fd.
let result = unsafe { libc::fstatfs(directory.as_raw_fd(), statfs.as_mut_ptr()) };
assert_eq!(result, 0);
// SAFETY: fstatfs succeeded and initialized the struct.
let statfs = unsafe { statfs.assume_init() };
assert_eq!(statfs.f_type, libc::OVERLAYFS_SUPER_MAGIC);
// SAFETY: Linux fsid_t contains two C int fields.
let fsid: [u32; 2] = unsafe { std::mem::transmute(statfs.f_fsid) };
assert_ne!(fsid, [0, 0], "fixture must have a nonzero backing fsid");
(path, directory)
}
}
37 changes: 28 additions & 9 deletions platform/sandbox/src/workspace/directory/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@
use cap_std::fs::Dir;
use std::{io, os::fd::AsRawFd};

pub(super) fn volume(directory: &Dir) -> io::Result<String> {
pub(super) struct Volume {
pub(super) id: String,
pub(super) stable_birth: bool,
}

pub(super) fn volume(directory: &Dir) -> io::Result<Volume> {
// SAFETY: fstatfs initializes the struct on success, and the descriptor is held.
let mut filesystem = std::mem::MaybeUninit::<libc::statfs>::uninit();
let result = unsafe { libc::fstatfs(directory.as_raw_fd(), filesystem.as_mut_ptr()) };
Expand All @@ -37,12 +42,18 @@ pub(super) fn volume(directory: &Dir) -> io::Result<String> {
if [first, second] == [0, 0] {
return Err(no_identity());
}
Ok(format!(
"linux-fsid:{:08x}:{first:08x}:{second:08x}",
filesystem.f_type
))
Ok(Volume {
id: format!(
"linux-fsid:{:08x}:{first:08x}:{second:08x}",
filesystem.f_type
),
stable_birth: true,
})
}
libc::BTRFS_SUPER_MAGIC => btrfs_volume(directory),
libc::BTRFS_SUPER_MAGIC => Ok(Volume {
id: btrfs_volume(directory)?,
stable_birth: true,
}),
libc::XFS_SUPER_MAGIC => {
// XFS f_fsid is a device number. Read its on-disk UUID via XFS_IOC_FSGEOMETRY_V1.
#[repr(C)]
Expand All @@ -59,15 +70,23 @@ pub(super) fn volume(directory: &Dir) -> io::Result<String> {
flags: [0; 7],
};
ioctl_read(&directory.open(".")?, b'X', 100, &mut geometry)?;
uuid(&geometry.uuid)
Ok(Volume {
id: uuid(&geometry.uuid)?,
stable_birth: true,
})
}
_ => {
// FS_IOC_GETFSUUID supplies the filesystem UUID on other filesystems
// that expose one. A missing UUID cannot authorize a durable grant.
// that expose one. Overlayfs needs its instance UUID: with uuid=off/null,
// f_fsid identifies the backing filesystem shared by independent overlays.
let mut fsuuid = [0_u8; 17];
ioctl_read(&directory.open(".")?, 0x15, 0, &mut fsuuid)?;
let size = usize::from(fsuuid[0]);
uuid(fsuuid.get(1..1 + size).ok_or_else(no_identity)?)
Ok(Volume {
id: uuid(fsuuid.get(1..1 + size).ok_or_else(no_identity)?)?,
// Copy-up replaces the lower inode's birth time with the upper's.
stable_birth: filesystem.f_type != libc::OVERLAYFS_SUPER_MAGIC,
})
}
}
}
Expand Down
Loading