use anyhow::{Context, Result, anyhow};
use caps::CapSet;
use libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall};
use tracing::{info, trace, warn};
#[allow(dead_code)]
pub fn sni_drop(dirs: &[&std::path::Path]) -> Result<()> {
use landlock::{
ABI, Access, AccessFs, AccessNet, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus,
Scope, path_beneath_rules,
};
drop_caps()?;
let abi = ABI::V6;
let status = Ruleset::default()
.handle_access(AccessFs::from_all(abi))?
.handle_access(AccessNet::BindTcp)?
.create()?
.set_no_new_privs(true)
.add_rules(path_beneath_rules(dirs, AccessFs::from_read(abi)))?
.restrict_self()?;
match status.ruleset {
RulesetStatus::FullyEnforced => {
info!("Landlock enabled and fully enforced for filesystem and network");
}
other => {
return Err(anyhow!(
"Landlock status not fully enforced for filesystem and network: {other:?}"
));
}
}
let status = Ruleset::default()
.scope(Scope::Signal)?
.create()?
.restrict_self()?;
match status.ruleset {
RulesetStatus::FullyEnforced => {
info!("Landlock enabled and fully enforced for signal");
}
other => warn!(
"Landlock status not fully enforced for signal (probably kernel <6.12): {other:?}"
),
}
match std::net::TcpListener::bind("127.0.0.1:0") {
Ok(_) => return Err(anyhow!("landlock failed to prevent tcp bind")),
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {}
Err(e) => {
return Err(anyhow!(
"unexpected error verifying landlock blocking connects: {e}"
));
}
}
Ok(())
}
pub fn drop_privs(with_rustls: bool) -> Result<()> {
landlock()?;
no_new_privs()?;
drop_caps()?;
seccomp(with_rustls)?;
Ok(())
}
fn no_new_privs() -> Result<()> {
trace!("Setting no new privs");
let ret = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };
if ret != 0 {
Err(anyhow!(
"prctl(no new privs): {}",
std::io::Error::last_os_error()
))
} else {
Ok(())
}
}
fn landlock() -> Result<()> {
use landlock::{ABI, Access, AccessFs, AccessNet, Ruleset, RulesetAttr, RulesetStatus, Scope};
let abi = ABI::V6;
let status = Ruleset::default()
.handle_access(AccessFs::from_all(abi))?
.handle_access(AccessNet::from_all(abi))?
.create()?
.restrict_self()?;
match status.ruleset {
RulesetStatus::FullyEnforced => {
info!("Landlock enabled and fully enforced for filesystem and network");
}
other => {
return Err(anyhow!(
"Landlock status not fully enforced for filesystem and network: {other:?}"
));
}
}
let status = Ruleset::default()
.scope(Scope::Signal)?
.scope(Scope::AbstractUnixSocket)?
.create()?
.restrict_self()?;
match status.ruleset {
RulesetStatus::FullyEnforced => {
info!("Landlock enabled and fully enforced for signal & abstract unix socket");
}
other => warn!(
"Landlock status not fully enforced for signal & abstract unix socket (probably kernel <6.12): {other:?}"
),
}
if std::fs::read_dir("/").is_ok() {
return Err(anyhow!("landlock failed to prevent listing root fs"));
}
match std::net::TcpStream::connect("127.0.0.1:8080") {
Ok(_) => return Err(anyhow!("landlock failed to prevent tcp connect")),
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {}
Err(e) => {
return Err(anyhow!(
"unexpected error verifying landlock blocking connects: {e}"
));
}
}
Ok(())
}
fn drop_caps() -> Result<()> {
trace!("Dropping caps");
for set in [
CapSet::Effective,
CapSet::Inheritable,
CapSet::Ambient,
CapSet::Permitted,
] {
caps::clear(None, set).context(format!("dropping privs for {set:?}"))?;
}
{
let set = CapSet::Bounding;
if let Err(e) = caps::clear(None, set) {
trace!("Expected: Dropping priv {set:?} failed: {e}");
}
}
Ok(())
}
fn seccomp(with_rustls: bool) -> Result<()> {
let mut f = ScmpFilterContext::new(ScmpAction::KillProcess)?;
for name in [
"write",
"close",
"futex",
"io_uring_enter",
"io_uring_register",
"getrandom",
] {
f.add_rule(ScmpAction::Allow, ScmpSyscall::from_name(name)?)?;
}
if with_rustls {
for name in [
"newfstatat",
"mmap",
"madvise",
"mprotect",
"munmap",
] {
f.add_rule(ScmpAction::Allow, ScmpSyscall::from_name(name)?)?;
}
}
f.load()?;
Ok(())
}