use eyre::{Result, eyre};
use landlock::{
ABI, AccessFs, BitFlags, Compatible, PathBeneath, PathFd, Ruleset, RulesetAttr,
RulesetCreatedAttr,
};
use super::SandboxConfig;
const SYSTEM_READ_PATHS: &[&str] = &[
"/usr",
"/lib",
"/lib64",
"/bin",
"/sbin",
"/etc",
"/proc",
"/sys",
"/nix",
"/snap",
"/home/linuxbrew",
];
fn add_read_rule(
ruleset: landlock::RulesetCreated,
path: &str,
access: BitFlags<AccessFs>,
) -> Result<landlock::RulesetCreated> {
match PathFd::new(path) {
Ok(fd) => ruleset
.add_rule(PathBeneath::new(fd, access))
.map_err(|e| eyre!("landlock add_rule failed for {path}: {e}")),
Err(_) => Ok(ruleset), }
}
fn add_path_rule(
ruleset: landlock::RulesetCreated,
path: &std::path::Path,
access: BitFlags<AccessFs>,
) -> Result<landlock::RulesetCreated> {
match PathFd::new(path) {
Ok(fd) => ruleset
.add_rule(PathBeneath::new(fd, access))
.map_err(|e| eyre!("landlock add_rule failed for {}: {e}", path.display())),
Err(_) => {
eprintln!(
"mise sandbox: path '{}' does not exist, sandbox rule may not apply as expected",
path.display()
);
Ok(ruleset)
}
}
}
pub fn apply_landlock(config: &SandboxConfig) -> Result<()> {
let abi = ABI::V5;
let read_access = AccessFs::from_read(abi);
let write_access = AccessFs::from_write(abi);
let full_access = read_access | write_access;
let deny_read = config.effective_deny_read();
let deny_write = config.effective_deny_write();
let handled_access = match (deny_read, deny_write) {
(true, true) => full_access,
(true, false) => read_access,
(false, true) => full_access, (false, false) => return Ok(()), };
let mut ruleset = Ruleset::default()
.handle_access(handled_access)
.map_err(|e| eyre!("failed to create landlock ruleset: {e}"))?
.set_compatibility(landlock::CompatLevel::BestEffort)
.create()
.map_err(|e| eyre!("failed to create landlock ruleset: {e}"))?;
if deny_read && deny_write {
for path in SYSTEM_READ_PATHS {
ruleset = add_read_rule(ruleset, path, read_access)?;
}
ruleset = add_read_rule(ruleset, "/tmp", full_access)?;
ruleset = add_read_rule(ruleset, "/dev", full_access)?;
let installs_dir: &std::path::Path = &crate::dirs::INSTALLS;
if installs_dir.exists() {
ruleset = add_path_rule(ruleset, installs_dir, read_access)?;
}
ruleset = add_path_rule(ruleset, &crate::env::MISE_DATA_DIR, read_access)?;
for path in &config.allow_read {
ruleset = add_path_rule(ruleset, path, read_access)?;
}
for path in &config.allow_write {
ruleset = add_path_rule(ruleset, path, full_access)?;
}
} else if deny_read {
for path in SYSTEM_READ_PATHS {
ruleset = add_read_rule(ruleset, path, read_access)?;
}
ruleset = add_read_rule(ruleset, "/tmp", read_access)?;
ruleset = add_read_rule(ruleset, "/dev", read_access)?;
let installs_dir: &std::path::Path = &crate::dirs::INSTALLS;
if installs_dir.exists() {
ruleset = add_path_rule(ruleset, installs_dir, read_access)?;
}
ruleset = add_path_rule(ruleset, &crate::env::MISE_DATA_DIR, read_access)?;
for path in &config.allow_read {
ruleset = add_path_rule(ruleset, path, read_access)?;
}
for path in &config.allow_write {
ruleset = add_path_rule(ruleset, path, read_access)?;
}
} else if deny_write {
ruleset = add_read_rule(ruleset, "/", read_access)?;
ruleset = add_read_rule(ruleset, "/tmp", full_access)?;
ruleset = add_read_rule(ruleset, "/dev", full_access)?;
for path in &config.allow_write {
ruleset = add_path_rule(ruleset, path, full_access)?;
}
}
ruleset
.restrict_self()
.map_err(|e| eyre!("failed to apply landlock restrictions: {e}"))?;
Ok(())
}