use std::{ffi::CString, fs::File, os::fd::AsRawFd, os::unix::ffi::OsStrExt, path::Path, ptr};
use anyhow::{Context, Result, bail};
use libc::{c_char, c_int, c_void};
unsafe extern "C" {
fn acl_get_file(path: *const c_char, kind: c_int) -> *mut c_void;
fn acl_get_fd(fd: c_int) -> *mut c_void;
fn acl_get_entry(acl: *mut c_void, entry_id: c_int, entry: *mut *mut c_void) -> c_int;
fn acl_get_tag_type(entry: *mut c_void, tag: *mut c_int) -> c_int;
fn acl_get_permset_mask_np(entry: *mut c_void, mask: *mut u64) -> c_int;
fn acl_free(acl: *mut c_void) -> c_int;
}
struct Acl(*mut c_void);
impl Drop for Acl {
fn drop(&mut self) {
unsafe { acl_free(self.0) };
}
}
pub(super) fn verify_path(path: &Path) -> Result<()> {
let path = CString::new(path.as_os_str().as_bytes())?;
verify(unsafe { acl_get_file(path.as_ptr(), 0x100) })
}
pub(super) fn verify_file(file: &File) -> Result<()> {
verify(unsafe { acl_get_fd(file.as_raw_fd()) })
}
fn verify(acl: *mut c_void) -> Result<()> {
if acl.is_null() {
let error = std::io::Error::last_os_error();
if error.raw_os_error() == Some(libc::ENOENT) {
return Ok(());
}
return Err(error).context("read cache extended ACL");
}
let acl = Acl(acl);
let mut entry_id = 0; const WRITE_MASK: u64 =
(1 << 2) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 8) | (1 << 10) | (1 << 12) | (1 << 13);
loop {
let mut entry = ptr::null_mut();
if unsafe { acl_get_entry(acl.0, entry_id, &mut entry) } != 0 {
let error = std::io::Error::last_os_error();
if error.raw_os_error() == Some(libc::EINVAL) {
return Ok(());
}
return Err(error).context("read cache extended ACL entry");
}
entry_id = -1; let mut tag = 0;
let mut mask = 0;
if unsafe { acl_get_tag_type(entry, &mut tag) } != 0
|| unsafe { acl_get_permset_mask_np(entry, &mut mask) } != 0
{
return Err(std::io::Error::last_os_error()).context("read cache ACL permissions");
}
if tag == 1 && mask & WRITE_MASK != 0 {
bail!("cache location has an extended ACL write grant");
}
}
}