use sandbox_core::{Result, SandboxError};
use std::path::PathBuf;
const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
const LANDLOCK_ACCESS_FS_EXECUTE: u64 = 1;
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 ALL_ACCESS_FS: 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;
const READ_ACCESS: u64 = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
const WRITE_ACCESS: u64 = LANDLOCK_ACCESS_FS_WRITE_FILE
| 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;
const EXEC_ACCESS: u64 = LANDLOCK_ACCESS_FS_EXECUTE;
#[derive(Debug, Clone, Default)]
pub struct LandlockConfig {
pub read_paths: Vec<PathBuf>,
pub write_paths: Vec<PathBuf>,
pub exec_paths: Vec<PathBuf>,
}
#[repr(C)]
struct LandlockRulesetAttr {
handled_access_fs: u64,
handled_access_net: u64,
}
#[repr(C)]
struct LandlockPathBeneathAttr {
allowed_access: u64,
parent_fd: i32,
}
const LANDLOCK_RULE_PATH_BENEATH: u32 = 1;
impl LandlockConfig {
pub fn is_available() -> bool {
let ret = unsafe {
libc::syscall(
libc::SYS_landlock_create_ruleset,
std::ptr::null::<libc::c_void>(),
0usize,
LANDLOCK_CREATE_RULESET_VERSION,
)
};
if ret >= 0 {
unsafe {
libc::close(ret as i32);
}
return true;
}
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
errno != libc::ENOSYS
}
pub fn apply(&self) -> Result<()> {
if !Self::is_available() {
return Err(SandboxError::FeatureNotAvailable(
"Landlock is not available on this kernel (requires Linux 5.13+)".to_string(),
));
}
let attr = LandlockRulesetAttr {
handled_access_fs: ALL_ACCESS_FS,
handled_access_net: 0,
};
let ruleset_fd = unsafe {
libc::syscall(
libc::SYS_landlock_create_ruleset,
&attr as *const LandlockRulesetAttr,
std::mem::size_of::<LandlockRulesetAttr>(),
0u32,
)
};
if ruleset_fd < 0 {
return Err(SandboxError::Landlock(format!(
"landlock_create_ruleset failed: {}",
std::io::Error::last_os_error()
)));
}
let ruleset_fd = ruleset_fd as i32;
let result = self.add_all_rules(ruleset_fd);
if result.is_err() {
unsafe {
libc::close(ruleset_fd);
}
return result;
}
unsafe {
if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 {
libc::close(ruleset_fd);
return Err(SandboxError::Landlock(format!(
"PR_SET_NO_NEW_PRIVS failed: {}",
std::io::Error::last_os_error()
)));
}
let ret = libc::syscall(libc::SYS_landlock_restrict_self, ruleset_fd, 0u32);
libc::close(ruleset_fd);
if ret < 0 {
return Err(SandboxError::Landlock(format!(
"landlock_restrict_self failed: {}",
std::io::Error::last_os_error()
)));
}
}
Ok(())
}
fn add_all_rules(&self, ruleset_fd: i32) -> Result<()> {
for path in &self.read_paths {
self.add_path_rule(ruleset_fd, path, READ_ACCESS)?;
}
for path in &self.write_paths {
self.add_path_rule(ruleset_fd, path, READ_ACCESS | WRITE_ACCESS)?;
}
for path in &self.exec_paths {
self.add_path_rule(ruleset_fd, path, READ_ACCESS | EXEC_ACCESS)?;
}
Ok(())
}
fn add_path_rule(&self, ruleset_fd: i32, path: &PathBuf, access: u64) -> Result<()> {
use std::os::unix::io::IntoRawFd;
let file = std::fs::File::open(path).map_err(|e| {
SandboxError::Landlock(format!(
"Failed to open path for landlock rule {}: {}",
path.display(),
e
))
})?;
let fd = file.into_raw_fd();
let attr = LandlockPathBeneathAttr {
allowed_access: access,
parent_fd: fd,
};
let ret = unsafe {
libc::syscall(
libc::SYS_landlock_add_rule,
ruleset_fd,
LANDLOCK_RULE_PATH_BENEATH,
&attr as *const LandlockPathBeneathAttr,
0u32,
)
};
unsafe {
libc::close(fd);
}
if ret < 0 {
return Err(SandboxError::Landlock(format!(
"landlock_add_rule failed for {}: {}",
path.display(),
std::io::Error::last_os_error()
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_landlock_config_default() {
let config = LandlockConfig::default();
assert!(config.read_paths.is_empty());
assert!(config.write_paths.is_empty());
assert!(config.exec_paths.is_empty());
}
#[test]
fn test_landlock_availability_check() {
let _ = LandlockConfig::is_available();
}
}