use std::fs::File;
use std::path::Path;
#[cfg(unix)]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
fn mmap_file_trust_boundary_issue(mode: u32, file_uid: u32, process_uid: u32) -> bool {
let group_or_other_writable = mode & 0o022 != 0;
let foreign_non_root_owner = file_uid != process_uid && file_uid != 0;
group_or_other_writable || foreign_non_root_owner
}
#[cfg(unix)]
const S_ISVTX: u32 = 0o1000;
#[cfg(unix)]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
fn mmap_parent_dir_trust_boundary_issue(mode: u32, dir_uid: u32, process_uid: u32) -> bool {
let foreign_non_root_owner = dir_uid != process_uid && dir_uid != 0;
let group_or_other_writable = mode & 0o022 != 0;
let sticky = mode & S_ISVTX != 0;
foreign_non_root_owner || (group_or_other_writable && !sticky)
}
#[cfg(unix)]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
fn current_uid() -> u32 {
unsafe extern "C" {
fn getuid() -> u32;
}
unsafe { getuid() }
}
#[cfg(unix)]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn reject_if_mmap_parent_directory_chain_weak(path: &Path) -> Result<(), String> {
use std::os::unix::fs::MetadataExt;
let canonical = path.canonicalize().map_err(|e| {
format!(
"failed to canonicalize {} for parent-directory trust check: {e}",
path.display()
)
})?;
let process_uid = current_uid();
let mut dir = canonical.parent();
while let Some(d) = dir {
let meta = std::fs::metadata(d)
.map_err(|e| format!("failed to stat parent directory {}: {e}", d.display()))?;
let mode = meta.mode();
let dir_uid = meta.uid();
if mmap_parent_dir_trust_boundary_issue(mode, dir_uid, process_uid) {
return Err(format!(
"refusing to load {}: parent directory {} is outside lattice's \
trust boundary (mode {:o}, dir uid {dir_uid}, process uid \
{process_uid}) -- writable by group/other without the sticky \
bit, or owned by a uid that is neither the process's uid nor \
root. A writable ancestor directory permits a rename-replace \
of the checkpoint file regardless of the file's own \
permissions. Fix the directory's permissions (not \
group/other writable, unless sticky) and, unless it is \
root-owned, its ownership before retrying.",
path.display(),
d.display(),
mode & 0o1777,
));
}
#[cfg(target_os = "macos")]
{
let dir_file = std::fs::File::open(d).map_err(|e| {
format!(
"failed to open parent directory {} for extended-ACL check: {e}",
d.display()
)
})?;
if let Some(perm_name) = macos_acl::acl_grants_rejected_permission(&dir_file, d)? {
return Err(format!(
"refusing to load {}: parent directory {} carries a macOS \
extended ACL entry granting {perm_name}, a permission that \
confers directory mutation (directly or transitively) to \
another principal. Extended ACLs are additive to POSIX \
mode bits and invisible to the mode/uid check above, so a \
0700 process-owned directory can still carry an ACE that \
lets a foreign principal create, delete, or rename-replace \
an entry beneath it -- including the checkpoint file \
itself -- before this gate's checkpoint is opened. Remove \
the ACL (`chmod -N {}`) before retrying.",
path.display(),
d.display(),
d.display(),
));
}
}
dir = d.parent();
}
Ok(())
}
#[cfg(not(unix))]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn reject_if_mmap_parent_directory_chain_weak(_path: &Path) -> Result<(), String> {
Ok(())
}
#[cfg(unix)]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn reject_if_mmap_file_trust_boundary_weak(
file: &File,
path: &Path,
) -> Result<(), String> {
use std::os::unix::fs::MetadataExt;
let meta = file
.metadata()
.map_err(|e| format!("failed to stat {}: {e}", path.display()))?;
let mode = meta.mode();
let file_uid = meta.uid();
let process_uid = current_uid();
if mmap_file_trust_boundary_issue(mode, file_uid, process_uid) {
return Err(format!(
"refusing to load {}: checkpoint file is outside lattice's trust \
boundary (mode {:o}, file uid {file_uid}, process uid {process_uid}) \
-- writable by group/other, or owned by a uid that is neither the \
process's uid nor root. This file is loaded via a read-only no-copy \
mmap, so a principal able to write to it could truncate or replace \
it in place between validation and the GPU reading the mapped \
pages; shape/bounds validation alone cannot defend against that. \
Accepted owners are this process's uid ({process_uid}) and root \
(0); this file's owner is uid {file_uid}. To comply, make the file \
non-group/other-writable (`chmod go-w {}`) and, unless it is \
root-owned, transfer ownership (`sudo chown {process_uid} {}`), \
then retry.",
path.display(),
mode & 0o777,
path.display(),
path.display(),
));
}
#[cfg(target_os = "macos")]
{
if let Some(perm_name) = macos_acl::acl_grants_rejected_permission(file, path)? {
return Err(format!(
"refusing to load {}: file carries a macOS extended ACL entry \
granting {perm_name}, a permission that confers write access \
(directly or transitively) to another principal. Extended ACLs \
are invisible to POSIX mode bits and can grant this even to an \
otherwise owner-locked-down file, bypassing the mode/uid check \
above. This file is loaded via a read-only no-copy mmap; remove \
the ACL (`chmod -N {}`) before retrying.",
path.display(),
path.display(),
));
}
}
Ok(())
}
#[cfg(not(unix))]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn reject_if_mmap_file_trust_boundary_weak(
_file: &File,
_path: &Path,
) -> Result<(), String> {
Ok(())
}
#[cfg(unix)]
use libc::{ELOOP, O_NOFOLLOW, O_NONBLOCK};
#[cfg(unix)]
const F_GETFL: i32 = 3;
#[cfg(unix)]
const F_SETFL: i32 = 4;
#[cfg(unix)]
unsafe extern "C" {
fn fcntl(fd: i32, cmd: i32, ...) -> i32;
}
#[cfg(unix)]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn open_trusted_mmap_file(path: &Path) -> Result<(File, std::fs::Metadata), String> {
use std::os::fd::AsRawFd;
use std::os::unix::fs::OpenOptionsExt;
let file = std::fs::OpenOptions::new()
.read(true)
.custom_flags(O_NONBLOCK | O_NOFOLLOW)
.open(path)
.map_err(|e| {
if e.raw_os_error() == Some(ELOOP) {
format!(
"refusing to open {}: final path component is a symlink -- \
checkpoint loads must name a regular file directly. \
O_NOFOLLOW rejects this before any trust or shape check \
runs, so a symlink planted at the final path component \
cannot redirect validation and mapping to a different, \
attacker-controlled file. This guards only the final \
path component -- a symlink earlier in the path (a \
parent directory) is still followed.",
path.display()
)
} else {
format!("failed to open {}: {e}", path.display())
}
})?;
let meta = file
.metadata()
.map_err(|e| format!("failed to stat {}: {e}", path.display()))?;
if !meta.is_file() {
return Err(format!(
"refusing to load {}: not a regular file -- checkpoint loads \
must be a plain file on disk. FIFOs, devices, sockets, and \
directories are rejected here, before any trust or shape \
check runs, so an attacker-planted node at the model path \
cannot block or misdirect the read-only load.",
path.display()
));
}
let fd = file.as_raw_fd();
let flags = unsafe { fcntl(fd, F_GETFL) };
if flags == -1 {
return Err(format!(
"failed to read fd flags for {}: {}",
path.display(),
std::io::Error::last_os_error()
));
}
if unsafe { fcntl(fd, F_SETFL, flags & !O_NONBLOCK) } == -1 {
return Err(format!(
"failed to clear O_NONBLOCK for {}: {}",
path.display(),
std::io::Error::last_os_error()
));
}
reject_if_mmap_parent_directory_chain_weak(path)?;
reject_if_mmap_file_trust_boundary_weak(&file, path)?;
Ok((file, meta))
}
#[cfg(not(unix))]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn open_trusted_mmap_file(path: &Path) -> Result<(File, std::fs::Metadata), String> {
let file = File::open(path).map_err(|e| format!("failed to open {}: {e}", path.display()))?;
let meta = file
.metadata()
.map_err(|e| format!("failed to stat {}: {e}", path.display()))?;
Ok((file, meta))
}
#[cfg(unix)]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn verify_mmap_target_unchanged(
file: &File,
prior: &std::fs::Metadata,
path: &Path,
) -> Result<(), String> {
use std::os::unix::fs::MetadataExt;
let now = file
.metadata()
.map_err(|e| format!("failed to re-stat {} after mmap: {e}", path.display()))?;
if now.size() != prior.size()
|| now.ino() != prior.ino()
|| now.dev() != prior.dev()
|| now.mtime() != prior.mtime()
|| now.mtime_nsec() != prior.mtime_nsec()
{
return Err(format!(
"refusing to trust mapped {}: file identity/content changed \
between pre-map validation and mmap (size {}->{}, ino {}->{}, \
dev {}->{}, mtime {}.{:09}->{}.{:09}) -- a truncate-or-replace \
race landed in the validate-then-map window",
path.display(),
prior.size(),
now.size(),
prior.ino(),
now.ino(),
prior.dev(),
now.dev(),
prior.mtime(),
prior.mtime_nsec(),
now.mtime(),
now.mtime_nsec(),
));
}
Ok(())
}
#[cfg(not(unix))]
#[cfg_attr(not(feature = "metal-gpu"), allow(dead_code))]
pub(crate) fn verify_mmap_target_unchanged(
_file: &File,
_prior: &std::fs::Metadata,
_path: &Path,
) -> Result<(), String> {
Ok(())
}
#[cfg(target_os = "macos")]
mod macos_acl {
use std::fs::File;
use std::os::fd::AsRawFd;
use std::os::raw::c_void;
use std::path::Path;
const ACL_TYPE_EXTENDED: i32 = 0x0000_0100;
const ACL_FIRST_ENTRY: i32 = 0;
const ACL_NEXT_ENTRY: i32 = -1;
const ACL_EXTENDED_ALLOW: i32 = 1;
#[cfg_attr(not(test), allow(dead_code))]
const ACL_READ_DATA: u32 = 1 << 1;
const ACL_WRITE_DATA: u32 = 1 << 2;
#[cfg_attr(not(test), allow(dead_code))]
const ACL_EXECUTE: u32 = 1 << 3;
const ACL_DELETE: u32 = 1 << 4;
const ACL_APPEND_DATA: u32 = 1 << 5;
const ACL_DELETE_CHILD: u32 = 1 << 6;
#[cfg_attr(not(test), allow(dead_code))]
const ACL_READ_ATTRIBUTES: u32 = 1 << 7;
const ACL_WRITE_ATTRIBUTES: u32 = 1 << 8;
#[cfg_attr(not(test), allow(dead_code))]
const ACL_READ_EXTATTRIBUTES: u32 = 1 << 9;
const ACL_WRITE_EXTATTRIBUTES: u32 = 1 << 10;
#[cfg_attr(not(test), allow(dead_code))]
const ACL_READ_SECURITY: u32 = 1 << 11;
const ACL_WRITE_SECURITY: u32 = 1 << 12;
const ACL_CHANGE_OWNER: u32 = 1 << 13;
#[cfg_attr(not(test), allow(dead_code))]
const ACL_SYNCHRONIZE: u32 = 1 << 20;
const REJECTED_PERMS: [(u32, &str); 8] = [
(ACL_WRITE_DATA, "ACL_WRITE_DATA"),
(ACL_APPEND_DATA, "ACL_APPEND_DATA"),
(ACL_DELETE, "ACL_DELETE"),
(ACL_DELETE_CHILD, "ACL_DELETE_CHILD"),
(ACL_WRITE_ATTRIBUTES, "ACL_WRITE_ATTRIBUTES"),
(ACL_WRITE_EXTATTRIBUTES, "ACL_WRITE_EXTATTRIBUTES"),
(ACL_WRITE_SECURITY, "ACL_WRITE_SECURITY"),
(ACL_CHANGE_OWNER, "ACL_CHANGE_OWNER"),
];
#[cfg(test)]
const HARMLESS_PERMS: [(u32, &str); 6] = [
(ACL_READ_DATA, "ACL_READ_DATA"),
(ACL_EXECUTE, "ACL_EXECUTE"),
(ACL_READ_ATTRIBUTES, "ACL_READ_ATTRIBUTES"),
(ACL_READ_EXTATTRIBUTES, "ACL_READ_EXTATTRIBUTES"),
(ACL_READ_SECURITY, "ACL_READ_SECURITY"),
(ACL_SYNCHRONIZE, "ACL_SYNCHRONIZE"),
];
const ENOENT: i32 = 2;
const EINVAL: i32 = 22;
unsafe extern "C" {
fn acl_get_fd_np(fd: i32, acl_type: i32) -> *mut c_void;
fn acl_get_entry(acl: *mut c_void, entry_id: i32, entry_p: *mut *mut c_void) -> i32;
fn acl_get_tag_type(entry: *mut c_void, tag_type_p: *mut i32) -> i32;
fn acl_get_permset(entry: *mut c_void, permset_p: *mut *mut c_void) -> i32;
fn acl_get_perm_np(permset: *mut c_void, perm: u32) -> i32;
fn acl_free(obj: *mut c_void) -> i32;
}
pub(super) fn acl_grants_rejected_permission(
file: &File,
path: &Path,
) -> Result<Option<&'static str>, String> {
let acl = unsafe { acl_get_fd_np(file.as_raw_fd(), ACL_TYPE_EXTENDED) };
if acl.is_null() {
let err = std::io::Error::last_os_error();
if matches!(err.raw_os_error(), Some(ENOENT) | Some(EINVAL)) {
return Ok(None);
}
return Err(format!(
"failed to read extended ACL for {}: {err}",
path.display()
));
}
let result = scan_acl_for_rejected_perm(acl, path);
unsafe {
acl_free(acl);
}
result
}
fn scan_acl_for_rejected_perm(
acl: *mut c_void,
path: &Path,
) -> Result<Option<&'static str>, String> {
let mut entry: *mut c_void = std::ptr::null_mut();
let mut entry_id = ACL_FIRST_ENTRY;
loop {
let is_next_entry_call = entry_id == ACL_NEXT_ENTRY;
let rc = unsafe { acl_get_entry(acl, entry_id, &mut entry) };
if rc != 0 {
let err = std::io::Error::last_os_error();
if is_next_entry_call && err.raw_os_error() == Some(EINVAL) {
break;
}
return Err(format!(
"failed to enumerate ACL entries for {}: acl_get_entry \
returned {rc} ({err}), which is not the documented \
end-of-list sentinel -- failing closed rather than \
silently truncating ACL enumeration",
path.display()
));
}
entry_id = ACL_NEXT_ENTRY;
let mut tag_type: i32 = 0;
if unsafe { acl_get_tag_type(entry, &mut tag_type) } != 0 {
return Err(format!(
"failed to read ACL entry tag type for {}: {} -- \
failing closed",
path.display(),
std::io::Error::last_os_error()
));
}
if tag_type != ACL_EXTENDED_ALLOW {
continue;
}
let mut permset: *mut c_void = std::ptr::null_mut();
if unsafe { acl_get_permset(entry, &mut permset) } != 0 {
return Err(format!(
"failed to read ACL entry permission set for {}: {} -- \
failing closed",
path.display(),
std::io::Error::last_os_error()
));
}
for (perm, name) in REJECTED_PERMS {
let rc = unsafe { acl_get_perm_np(permset, perm) };
match rc {
1 => return Ok(Some(name)),
0 => continue,
_ => {
return Err(format!(
"failed to check ACL permission {name} for {}: \
acl_get_perm_np returned {rc} ({}) -- failing \
closed",
path.display(),
std::io::Error::last_os_error()
));
}
}
}
}
Ok(None)
}
#[cfg(test)]
mod fail_closed_tests {
use super::*;
use std::os::fd::AsRawFd;
#[test]
fn out_of_range_entry_id_reproduces_a_non_end_of_list_einval() {
let tmp = tempfile::tempdir().expect("tempdir create");
let path = tmp.path().join("fail_closed_probe.q4");
std::fs::write(&path, b"not a real checkpoint, only the ACL matters here")
.expect("write tempfile");
let status = std::process::Command::new("chmod")
.arg("+a")
.arg("everyone allow read")
.arg(&path)
.status()
.expect("run chmod +a");
assert!(
status.success(),
"chmod +a must succeed to set up this test"
);
let file = File::open(&path).expect("open tempfile");
let acl = unsafe { acl_get_fd_np(file.as_raw_fd(), ACL_TYPE_EXTENDED) };
assert!(
!acl.is_null(),
"acl_get_fd_np must succeed: the ACE set up above must be present"
);
let bogus_entry_id = 12345;
assert_ne!(
bogus_entry_id, ACL_NEXT_ENTRY,
"sanity: the probe id must not accidentally equal the sentinel id"
);
let mut entry: *mut c_void = std::ptr::null_mut();
let rc = unsafe { acl_get_entry(acl, bogus_entry_id, &mut entry) };
let err = std::io::Error::last_os_error();
unsafe {
acl_free(acl);
}
assert_ne!(rc, 0, "an out-of-range entry_id must fail, not succeed");
assert_eq!(
err.raw_os_error(),
Some(EINVAL),
"documented failure mode for an invalid entry_id is EINVAL -- \
the same errno the genuine end-of-list sentinel uses, which is \
exactly why `scan_acl_for_rejected_perm` must not treat \
errno==EINVAL alone as sufficient"
);
let is_next_entry_call = bogus_entry_id == ACL_NEXT_ENTRY;
assert!(
!is_next_entry_call,
"this call is not the ACL_NEXT_ENTRY continuation the sentinel \
requires, so `scan_acl_for_rejected_perm` must classify this \
exact (rc, errno) pair as a failure to fail closed on, not as \
end-of-list"
);
}
}
#[cfg(test)]
mod classification_tests {
use super::*;
use std::collections::HashSet;
const ALL_ACL_PERM_T_VALUES: [u32; 14] = [
1 << 1,
1 << 2,
1 << 3,
1 << 4,
1 << 5,
1 << 6,
1 << 7,
1 << 8,
1 << 9,
1 << 10,
1 << 11,
1 << 12,
1 << 13,
1 << 20,
];
#[test]
fn rejected_and_harmless_perms_exactly_partition_the_full_acl_perm_t_enum() {
let rejected: HashSet<u32> = REJECTED_PERMS.iter().map(|(v, _)| *v).collect();
let harmless: HashSet<u32> = HARMLESS_PERMS.iter().map(|(v, _)| *v).collect();
let all: HashSet<u32> = ALL_ACL_PERM_T_VALUES.iter().copied().collect();
assert_eq!(
rejected.len(),
REJECTED_PERMS.len(),
"REJECTED_PERMS must not contain a duplicate value"
);
assert_eq!(
harmless.len(),
HARMLESS_PERMS.len(),
"HARMLESS_PERMS must not contain a duplicate value"
);
assert!(
rejected.is_disjoint(&harmless),
"a perm classified as both rejected and harmless is a \
contradiction, not just redundant"
);
let union: HashSet<u32> = rejected.union(&harmless).copied().collect();
assert_eq!(
union, all,
"REJECTED_PERMS ∪ HARMLESS_PERMS must cover every acl_perm_t \
value exactly once -- a value present in `all` but missing \
here is an unclassified perm (must default-reject per this \
round's ruling, i.e. belong in REJECTED_PERMS unless proven \
harmless); a value here but absent from `all` is stale"
);
let Some(sdk_path) = std::process::Command::new("xcrun")
.args(["--show-sdk-path"])
.output()
.ok()
.filter(|out| out.status.success())
.map(|out| String::from_utf8_lossy(&out.stdout).trim().to_string())
else {
eprintln!(
"SKIP: `xcrun --show-sdk-path` unavailable -- skipping SDK-header-bound \
acl_perm_t classification check"
);
return;
};
let header_path = std::path::Path::new(&sdk_path).join("usr/include/sys/acl.h");
let Ok(header) = std::fs::read_to_string(&header_path) else {
eprintln!(
"SKIP: {} not present -- skipping SDK-header-bound acl_perm_t \
classification check",
header_path.display()
);
return;
};
const PREFIX: &str = "__DARWIN_ACL_";
let mut defines: std::collections::HashMap<&str, u32> =
std::collections::HashMap::new();
for line in header.lines() {
let Some(rest) = line
.trim()
.strip_prefix("#define ")
.and_then(|s| s.strip_prefix(PREFIX))
else {
continue;
};
let Some((name, value_str)) = rest.split_once(char::is_whitespace) else {
continue;
};
let value_str = value_str.trim();
if let Some(shift_str) = value_str
.strip_prefix("(1<<")
.and_then(|s| s.strip_suffix(')'))
&& let Ok(shift) = shift_str.trim().parse::<u32>()
{
defines.insert(name, 1u32 << shift);
}
}
for line in header.lines() {
let Some(rest) = line
.trim()
.strip_prefix("#define ")
.and_then(|s| s.strip_prefix(PREFIX))
else {
continue;
};
let Some((name, value_str)) = rest.split_once(char::is_whitespace) else {
continue;
};
if defines.contains_key(name) {
continue;
}
if let Some(alias) = value_str.trim().strip_prefix(PREFIX)
&& let Some(&v) = defines.get(alias)
{
defines.insert(name, v);
}
}
let block_end = header.find("} acl_perm_t;").unwrap_or_else(|| {
panic!(
"could not find `}} acl_perm_t;` in {} -- the header's enum \
format changed and this test's parser needs updating",
header_path.display()
)
});
let block_start = header[..block_end]
.rfind("typedef enum {")
.unwrap_or_else(|| {
panic!(
"found `}} acl_perm_t;` in {} but no preceding `typedef enum {{` -- \
the header's enum format changed and this test's parser needs updating",
header_path.display()
)
});
let enum_block = &header[block_start..block_end];
let mut header_perms: HashSet<u32> = HashSet::new();
for line in enum_block.lines() {
let Some(idx) = line.find(PREFIX) else {
continue;
};
let rest = &line[idx + PREFIX.len()..];
let ident_end = rest
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
.unwrap_or(rest.len());
let name = &rest[..ident_end];
if let Some(&v) = defines.get(name) {
header_perms.insert(v);
}
}
assert!(
!header_perms.is_empty(),
"parsed zero acl_perm_t perms out of {} -- the header's enum/macro \
format changed and this test's parser needs updating, not silently \
passing vacuously",
header_path.display()
);
assert_eq!(
union,
header_perms,
"this module's REJECTED_PERMS ∪ HARMLESS_PERMS must classify EXACTLY \
the acl_perm_t perms {} currently defines -- a perm the header defines \
but this module doesn't classify is a real gap (a Darwin SDK addition \
landing unclassified, silently treated as harmless-by-omission); a \
perm classified here but absent from the header is stale and should \
be removed",
header_path.display()
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(unix)]
#[test]
fn mmap_file_trust_boundary_issue_fires_on_group_or_other_writable_or_foreign_non_root_owner() {
assert!(
mmap_file_trust_boundary_issue(0o664, 1000, 1000),
"group-writable (0o664) must trigger the trust-boundary check"
);
assert!(
mmap_file_trust_boundary_issue(0o646, 1000, 1000),
"other-writable (0o646) must trigger the trust-boundary check"
);
assert!(
!mmap_file_trust_boundary_issue(0o644, 1000, 1000),
"owner-writable-only, group/other read-only must not trigger the trust-boundary check"
);
assert!(
!mmap_file_trust_boundary_issue(0o600, 1000, 1000),
"owner-only rwx must not trigger the trust-boundary check"
);
assert!(
mmap_file_trust_boundary_issue(0o400, 999, 1000),
"a file not owned by the current uid (and not root) must trigger the \
trust-boundary check even when read-only"
);
assert!(
!mmap_file_trust_boundary_issue(0o644, 0, 1000),
"a root-owned, owner-read-only file must be accepted for a \
non-root process uid -- root-owned shared model directories are \
a documented deployment shape, not an attack"
);
assert!(
mmap_file_trust_boundary_issue(0o664, 0, 1000),
"root ownership does not excuse group/other-writability"
);
}
#[cfg(unix)]
#[test]
fn reject_if_mmap_file_trust_boundary_weak_fails_closed_on_writable_file() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir create");
let path = tmp.path().join("world_writable.q4");
std::fs::write(
&path,
b"not a real checkpoint, only permissions matter here",
)
.expect("write tempfile");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o666))
.expect("chmod 0o666");
let file = File::open(&path).expect("open tempfile");
let result = reject_if_mmap_file_trust_boundary_weak(&file, &path);
assert!(
result.is_err(),
"a group/other-writable checkpoint file must be refused, not merely warned about"
);
let msg = result.expect_err("checked is_err above");
assert!(
msg.contains("refusing to load") && msg.contains(&path.display().to_string()),
"error must state the load is refused and name the offending path; got: {msg}"
);
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
.expect("chmod 0o600");
let private_file = File::open(&path).expect("reopen tempfile after chmod");
assert!(
reject_if_mmap_file_trust_boundary_weak(&private_file, &path).is_ok(),
"an owner-only file owned by the current process must be accepted"
);
}
#[cfg(unix)]
#[test]
fn open_trusted_mmap_file_rejects_a_writable_parent_directory() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir create");
let writable_dir = tmp.path().join("writable_parent");
std::fs::create_dir(&writable_dir).expect("create parent dir fixture");
std::fs::set_permissions(&writable_dir, std::fs::Permissions::from_mode(0o777))
.expect("chmod parent dir 0o777 (no sticky bit)");
let path = writable_dir.join("checkpoint.q4");
std::fs::write(
&path,
b"not a real checkpoint, only the parent dir matters here",
)
.expect("write tempfile");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
.expect("chmod checkpoint file 0o600");
let err = open_trusted_mmap_file(&path).expect_err(
"a checkpoint under a group/other-writable, non-sticky parent directory \
must be refused even though the file itself is owner-only",
);
assert!(
err.contains("parent directory"),
"error must name the parent directory as the rejection cause; got: {err}"
);
}
#[cfg(unix)]
#[test]
fn open_trusted_mmap_file_accepts_a_sticky_world_writable_parent_directory() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir create");
let sticky_dir = tmp.path().join("sticky_parent");
std::fs::create_dir(&sticky_dir).expect("create parent dir fixture");
std::fs::set_permissions(&sticky_dir, std::fs::Permissions::from_mode(0o1777))
.expect("chmod parent dir 0o1777 (world-writable + sticky)");
let path = sticky_dir.join("checkpoint.q4");
std::fs::write(
&path,
b"not a real checkpoint, only the parent dir matters here",
)
.expect("write tempfile");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
.expect("chmod checkpoint file 0o600");
open_trusted_mmap_file(&path).expect(
"a checkpoint under a sticky world-writable parent directory must be \
accepted -- the sticky bit already prevents the rename-replace this \
check exists to stop",
);
}
#[cfg(target_os = "macos")]
#[test]
fn reject_if_mmap_parent_directory_chain_weak_fails_closed_on_ancestor_acl_grant() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir create");
let ancestor = tmp.path().join("acl_ancestor");
std::fs::create_dir(&ancestor).expect("create ancestor dir fixture");
std::fs::set_permissions(&ancestor, std::fs::Permissions::from_mode(0o700))
.expect("chmod ancestor dir 0o700 (owner-only, passes the mode/uid check)");
let status = std::process::Command::new("chmod")
.arg("+a")
.arg("everyone allow add_file")
.arg(&ancestor)
.status()
.expect("run chmod +a");
if !status.success() {
eprintln!(
"skipping reject_if_mmap_parent_directory_chain_weak_fails_closed_on_ancestor_acl_grant: \
this sandbox cannot set an extended ACL via chmod +a"
);
return;
}
let path = ancestor.join("checkpoint.q4");
std::fs::write(
&path,
b"not a real checkpoint, only the ancestor ACL matters here",
)
.expect("write tempfile");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
.expect("chmod checkpoint file 0o600");
let result = reject_if_mmap_parent_directory_chain_weak(&path);
assert!(
result.is_err(),
"a 0700 process-owned ancestor directory carrying an extended ACL that \
grants a foreign principal add_file (directory-mutating) rights must \
still be refused -- that ACE is invisible to the mode/uid check alone \
and lets the grantee replace the checkpoint entry before it is opened"
);
let msg = result.expect_err("checked is_err above");
assert!(
msg.contains("extended ACL") && msg.contains(&ancestor.display().to_string()),
"error must name the extended-ACL cause and the offending ancestor; got: {msg}"
);
}
#[test]
fn open_trusted_mmap_file_rejects_a_fifo_without_blocking() {
let tmp = tempfile::tempdir().expect("tempdir create");
let path = tmp.path().join("planted.q4");
let status = std::process::Command::new("mkfifo")
.arg(&path)
.status()
.expect("run mkfifo");
assert!(status.success(), "mkfifo must succeed to set up this test");
let (tx, rx) = std::sync::mpsc::channel();
let probe_path = path.clone();
std::thread::spawn(move || {
let result = open_trusted_mmap_file(&probe_path);
let _ = tx.send(result);
});
let result = rx.recv_timeout(std::time::Duration::from_secs(5)).expect(
"open_trusted_mmap_file did not return within 5s -- it blocked on the \
planted FIFO, meaning the O_NONBLOCK open-time guard regressed",
);
let err = result.expect_err("a FIFO must be rejected, not accepted, for a mmap load");
assert!(
err.contains("not a regular file"),
"error must name the FIFO as the non-regular-file rejection cause; got: {err}"
);
}
#[test]
fn open_trusted_mmap_file_rejects_a_directory() {
let tmp = tempfile::tempdir().expect("tempdir create");
let dir_path = tmp.path().join("planted_dir.q4");
std::fs::create_dir(&dir_path).expect("create planted directory");
let err = open_trusted_mmap_file(&dir_path)
.expect_err("a directory must be rejected, not accepted, for a mmap load");
assert!(
err.contains("not a regular file"),
"error must name the directory as the non-regular-file rejection cause; got: {err}"
);
}
#[test]
fn open_trusted_mmap_file_accepts_a_regular_owner_only_file() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir create");
let path = tmp.path().join("real.q4");
std::fs::write(
&path,
b"not a real checkpoint, just needs to be a regular file",
)
.expect("write tempfile");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
.expect("chmod 0o600");
let (file, meta) =
open_trusted_mmap_file(&path).expect("a regular owner-only file must be accepted");
assert!(meta.is_file());
assert_eq!(
file.metadata().expect("stat returned file").len(),
meta.len()
);
}
#[test]
fn open_trusted_mmap_file_rejects_a_symlinked_final_component() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir create");
let real_path = tmp.path().join("real.q4");
std::fs::write(&real_path, b"a real checkpoint-shaped regular file")
.expect("write real fixture");
std::fs::set_permissions(&real_path, std::fs::Permissions::from_mode(0o600))
.expect("chmod 0o600");
let symlink_path = tmp.path().join("planted_symlink.q4");
std::os::unix::fs::symlink(&real_path, &symlink_path).expect("create symlink fixture");
let err = open_trusted_mmap_file(&symlink_path).expect_err(
"a symlink at the final path component must be rejected, not followed to its target",
);
assert!(
err.contains("symlink"),
"error must name the symlink as the rejection cause; got: {err}"
);
open_trusted_mmap_file(&real_path)
.expect("the real (non-symlink) file must still be accepted directly");
}
#[test]
fn verify_mmap_target_unchanged_rejects_a_truncate_after_validate_race() {
let tmp = tempfile::tempdir().expect("tempdir create");
let path = tmp.path().join("truncate_race.q4");
std::fs::write(&path, vec![0xABu8; 4096]).expect("write fixture");
let (file, prior_meta) =
open_trusted_mmap_file(&path).expect("open + trust-gate the fixture");
let _mmap = unsafe { memmap2::MmapOptions::new().map(&file) }.expect("mmap fixture");
std::fs::OpenOptions::new()
.write(true)
.open(&path)
.expect("reopen fixture for truncation")
.set_len(4096 - 8)
.expect("truncate fixture in place");
let result = verify_mmap_target_unchanged(&file, &prior_meta, &path);
assert!(
result.is_err(),
"a size change between the pre-map stat and this recheck must be rejected"
);
let msg = result.expect_err("checked is_err above");
assert!(
msg.contains("refusing to trust mapped") && msg.contains("size"),
"error must state the mapping is untrusted and cite the size mismatch; got: {msg}"
);
}
#[test]
fn verify_mmap_target_unchanged_accepts_an_unmodified_file() {
let tmp = tempfile::tempdir().expect("tempdir create");
let path = tmp.path().join("unchanged.q4");
std::fs::write(&path, vec![0xCDu8; 4096]).expect("write fixture");
let (file, prior_meta) =
open_trusted_mmap_file(&path).expect("open + trust-gate the fixture");
let _mmap = unsafe { memmap2::MmapOptions::new().map(&file) }.expect("mmap fixture");
assert!(
verify_mmap_target_unchanged(&file, &prior_meta, &path).is_ok(),
"a file untouched between the pre-map stat and the post-map recheck must be accepted"
);
}
#[cfg(target_os = "macos")]
mod macos_acl_regression_tests {
use super::*;
use std::os::unix::fs::PermissionsExt;
fn tempfile_with_ace(
dir: &std::path::Path,
name: &str,
ace: &str,
) -> (std::path::PathBuf, File) {
let path = dir.join(name);
std::fs::write(&path, b"not a real checkpoint, only the ACL matters here")
.expect("write tempfile");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
.expect("chmod 0o600");
let status = std::process::Command::new("chmod")
.arg("+a")
.arg(ace)
.arg(&path)
.status()
.expect("run chmod +a");
assert!(
status.success(),
"chmod +a must succeed to set up this test: {ace}"
);
let file = File::open(&path).expect("open tempfile after chmod +a");
(path, file)
}
#[test]
fn no_acl_is_accepted() {
let tmp = tempfile::tempdir().expect("tempdir create");
let path = tmp.path().join("no_acl.q4");
std::fs::write(&path, b"not a real checkpoint, only the ACL matters here")
.expect("write tempfile");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
.expect("chmod 0o600");
let file = File::open(&path).expect("open tempfile");
assert!(
reject_if_mmap_file_trust_boundary_weak(&file, &path).is_ok(),
"a 0600 owner-matched file with no extended ACL must be accepted (control)"
);
}
#[test]
fn content_write_grant_is_rejected() {
let tmp = tempfile::tempdir().expect("tempdir create");
let (path, file) = tempfile_with_ace(
tmp.path(),
"content_write.q4",
"everyone allow write,writeattr,writeextattr,delete",
);
let result = reject_if_mmap_file_trust_boundary_weak(&file, &path);
assert!(
result.is_err(),
"a file carrying an extended ACL that grants content-write access to \
another principal must be refused even though st_mode reports 0600 \
owner-only"
);
let msg = result.expect_err("checked is_err above");
assert!(
msg.contains("extended ACL"),
"error must name the extended-ACL cause; got: {msg}"
);
}
#[test]
fn write_security_only_grant_is_rejected() {
let tmp = tempfile::tempdir().expect("tempdir create");
let (path, file) = tempfile_with_ace(
tmp.path(),
"write_security_only.q4",
"everyone allow writesecurity",
);
let result = reject_if_mmap_file_trust_boundary_weak(&file, &path);
assert!(
result.is_err(),
"an ACE granting only ACL_WRITE_SECURITY must be rejected -- it lets \
the grantee rewrite the ACL to grant themselves write later"
);
assert!(
result
.expect_err("checked is_err above")
.contains("ACL_WRITE_SECURITY")
);
}
#[test]
fn change_owner_only_grant_is_rejected() {
let tmp = tempfile::tempdir().expect("tempdir create");
let (path, file) =
tempfile_with_ace(tmp.path(), "change_owner_only.q4", "everyone allow chown");
let result = reject_if_mmap_file_trust_boundary_weak(&file, &path);
assert!(
result.is_err(),
"an ACE granting only ACL_CHANGE_OWNER must be rejected -- it lets \
the grantee take ownership and then re-grant themselves write"
);
assert!(
result
.expect_err("checked is_err above")
.contains("ACL_CHANGE_OWNER")
);
}
#[test]
fn harmless_read_only_grant_is_accepted() {
let tmp = tempfile::tempdir().expect("tempdir create");
let (path, file) = tempfile_with_ace(tmp.path(), "read_only.q4", "everyone allow read");
assert!(
reject_if_mmap_file_trust_boundary_weak(&file, &path).is_ok(),
"a harmless read-only ACE must be accepted -- the policy allowlists \
harmless perms, it does not reject every ACE unconditionally"
);
}
}
}