use aft::sandbox_profile::SandboxProfile;
use landlock::{
AccessFs, CompatLevel, Compatible, LandlockStatus, PathBeneath, PathFd, Ruleset, RulesetAttr,
RulesetCreatedAttr, RulesetStatus, ABI,
};
use std::path::Path;
pub(super) const TARGET_ABI: ABI = ABI::V7;
pub(super) const REQUIRED_WRITE_ABI: ABI = ABI::V3;
#[derive(Debug, Clone, Copy)]
pub(super) struct AppliedLandlock {
pub effective_abi: ABI,
pub partially_enforced: bool,
}
pub(super) fn apply(profile: &SandboxProfile) -> Result<AppliedLandlock, String> {
apply_paths(profile.write_allow_roots())
}
pub(super) fn probe() -> Result<AppliedLandlock, String> {
apply_paths(vec![Path::new("/")])
}
fn apply_paths(paths: Vec<&Path>) -> Result<AppliedLandlock, String> {
let write_access = AccessFs::from_write(TARGET_ABI) & !AccessFs::IoctlDev;
let mut ruleset = Ruleset::default()
.set_compatibility(CompatLevel::BestEffort)
.handle_access(write_access)
.map_err(|error| format!("failed to configure Landlock write access: {error}"))?
.create()
.map_err(|error| format!("failed to create Landlock ruleset: {error}"))?;
for path in paths {
let path_fd = PathFd::new(path)
.map_err(|error| format!("failed to open Landlock path {}: {error}", path.display()))?;
ruleset = ruleset
.add_rule(PathBeneath::new(path_fd, write_access))
.map_err(|error| {
format!(
"failed to grant Landlock writes beneath {}: {error}",
path.display()
)
})?;
}
let status = ruleset
.restrict_self()
.map_err(|error| format!("failed to restrict process with Landlock: {error}"))?;
if status.ruleset == RulesetStatus::NotEnforced {
return Err("Landlock ruleset was not enforced".to_string());
}
let effective_abi = match status.landlock {
LandlockStatus::Available { effective_abi, .. } => effective_abi,
LandlockStatus::NotEnabled => return Err("Landlock is disabled by the kernel".to_string()),
LandlockStatus::NotImplemented => {
return Err("Landlock is not implemented by this kernel".to_string());
}
};
Ok(AppliedLandlock {
effective_abi,
partially_enforced: status.ruleset == RulesetStatus::PartiallyEnforced,
})
}
pub(super) fn abi_label(abi: ABI) -> String {
if abi == ABI::Unsupported {
"unsupported".to_string()
} else {
format!("V{abi}")
}
}