use std::ffi::CString;
use std::io;
use std::path::PathBuf;
const LANDLOCK_CREATE_RULESET_VERSION: libc::c_long = 1 << 0;
const LANDLOCK_RULE_PATH_BENEATH: libc::c_long = 1;
const LANDLOCK_ACCESS_FS_EXECUTE: u64 = 1 << 0;
const LANDLOCK_ACCESS_FS_WRITE_FILE: u64 = 1 << 1;
const LANDLOCK_ACCESS_FS_READ_FILE: u64 = 1 << 2;
const LANDLOCK_ACCESS_FS_READ_DIR: u64 = 1 << 3;
const LANDLOCK_ACCESS_FS_REMOVE_DIR: u64 = 1 << 4;
const LANDLOCK_ACCESS_FS_REMOVE_FILE: u64 = 1 << 5;
const LANDLOCK_ACCESS_FS_MAKE_CHAR: u64 = 1 << 6;
const LANDLOCK_ACCESS_FS_MAKE_DIR: u64 = 1 << 7;
const LANDLOCK_ACCESS_FS_MAKE_REG: u64 = 1 << 8;
const LANDLOCK_ACCESS_FS_MAKE_SOCK: u64 = 1 << 9;
const LANDLOCK_ACCESS_FS_MAKE_FIFO: u64 = 1 << 10;
const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u64 = 1 << 11;
const LANDLOCK_ACCESS_FS_MAKE_SYM: u64 = 1 << 12;
const LANDLOCK_ACCESS_FS_REFER: u64 = 1 << 13;
const LANDLOCK_ACCESS_FS_TRUNCATE: u64 = 1 << 14;
const LANDLOCK_ACCESS_FS_IOCTL_DEV: u64 = 1 << 15;
pub const FS_ACCESS_RO: u64 =
LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
pub const FS_ACCESS_RW: u64 = LANDLOCK_ACCESS_FS_EXECUTE
| LANDLOCK_ACCESS_FS_WRITE_FILE
| LANDLOCK_ACCESS_FS_READ_FILE
| LANDLOCK_ACCESS_FS_READ_DIR
| LANDLOCK_ACCESS_FS_REMOVE_DIR
| LANDLOCK_ACCESS_FS_REMOVE_FILE
| LANDLOCK_ACCESS_FS_MAKE_CHAR
| LANDLOCK_ACCESS_FS_MAKE_DIR
| LANDLOCK_ACCESS_FS_MAKE_REG
| LANDLOCK_ACCESS_FS_MAKE_SOCK
| LANDLOCK_ACCESS_FS_MAKE_FIFO
| LANDLOCK_ACCESS_FS_MAKE_BLOCK
| LANDLOCK_ACCESS_FS_MAKE_SYM
| LANDLOCK_ACCESS_FS_REFER
| LANDLOCK_ACCESS_FS_TRUNCATE
| LANDLOCK_ACCESS_FS_IOCTL_DEV;
#[repr(C)]
struct LandlockRulesetAttr {
handled_access_fs: u64,
}
#[repr(C, packed)]
struct LandlockPathBeneathAttr {
allowed_access: u64,
parent_fd: i32,
}
#[derive(Clone, Debug)]
pub struct LandlockRule {
pub path: PathBuf,
pub access: u64,
}
pub fn get_abi_version() -> u32 {
let ret = unsafe {
libc::syscall(
libc::SYS_landlock_create_ruleset,
std::ptr::null::<LandlockRulesetAttr>(),
0usize,
LANDLOCK_CREATE_RULESET_VERSION,
)
};
if ret < 0 {
0
} else {
ret as u32
}
}
fn fs_access_mask_for_abi(abi: u32) -> u64 {
let mut mask: u64 = (1 << 13) - 1;
if abi >= 2 {
mask |= LANDLOCK_ACCESS_FS_REFER;
}
if abi >= 3 {
mask |= LANDLOCK_ACCESS_FS_TRUNCATE;
}
if abi >= 4 {
mask |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
}
mask
}
pub fn apply_landlock(rules: &[LandlockRule]) -> io::Result<()> {
if rules.is_empty() {
return Ok(());
}
let abi = get_abi_version();
if abi == 0 {
return Ok(()); }
let (ruleset_fd, handled_access) = {
let mut abi_try = abi;
loop {
let mask = fs_access_mask_for_abi(abi_try);
let attr = LandlockRulesetAttr {
handled_access_fs: mask,
};
let ret = unsafe {
libc::syscall(
libc::SYS_landlock_create_ruleset,
&attr as *const LandlockRulesetAttr,
std::mem::size_of::<LandlockRulesetAttr>() as libc::size_t,
0i64,
)
};
if ret >= 0 {
break (ret as i32, mask);
}
let e = io::Error::last_os_error();
match e.raw_os_error() {
Some(libc::ENOSYS) => return Ok(()), Some(libc::EINVAL) if abi_try > 1 => {
abi_try -= 1; }
_ => return Err(e),
}
}
};
for rule in rules {
let cpath = match CString::new(rule.path.to_string_lossy().as_ref()) {
Ok(s) => s,
Err(_) => continue, };
let path_fd = unsafe { libc::open(cpath.as_ptr(), libc::O_PATH | libc::O_CLOEXEC) };
if path_fd < 0 {
continue;
}
let allowed = rule.access & handled_access;
let path_attr = LandlockPathBeneathAttr {
allowed_access: allowed,
parent_fd: path_fd,
};
unsafe {
libc::syscall(
libc::SYS_landlock_add_rule,
ruleset_fd as libc::c_long,
LANDLOCK_RULE_PATH_BENEATH,
&path_attr as *const LandlockPathBeneathAttr,
0i64,
);
libc::close(path_fd);
}
}
let ret = unsafe {
libc::syscall(
libc::SYS_landlock_restrict_self,
ruleset_fd as libc::c_long,
0i64,
)
};
unsafe { libc::close(ruleset_fd) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}