#[cfg(target_os = "linux")]
#[derive(Debug, Clone, Copy)]
struct ForbiddenCap {
name: &'static str,
value: i32,
}
#[cfg(target_os = "linux")]
const FORBIDDEN: &[ForbiddenCap] = &[
ForbiddenCap {
name: "CAP_SYS_ADMIN",
value: 21,
},
ForbiddenCap {
name: "CAP_BPF",
value: 39,
},
ForbiddenCap {
name: "CAP_PERFMON",
value: 38,
},
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrivilegeError {
pub held: Vec<String>,
}
impl std::fmt::Display for PrivilegeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"aa-runtime must not hold BPF-class capabilities (delegated to aa-ebpf-loaderd), \
but the effective set contains: {}",
self.held.join(", ")
)
}
}
impl std::error::Error for PrivilegeError {}
pub fn enforce_least_privilege() -> Result<(), PrivilegeError> {
#[cfg(target_os = "linux")]
{
drop_forbidden_from_bounding_set();
let held = effective_forbidden_caps();
if !held.is_empty() {
return Err(PrivilegeError { held });
}
tracing::info!("least-privilege self-check passed: no CAP_BPF/CAP_PERFMON/CAP_SYS_ADMIN held");
Ok(())
}
#[cfg(not(target_os = "linux"))]
{
Ok(())
}
}
#[cfg(target_os = "linux")]
fn drop_forbidden_from_bounding_set() {
const PR_CAPBSET_DROP: i32 = 24;
for cap in FORBIDDEN {
let _ = unsafe { libc::prctl(PR_CAPBSET_DROP, cap.value as libc::c_ulong, 0, 0, 0) };
}
}
#[cfg(target_os = "linux")]
fn effective_forbidden_caps() -> Vec<String> {
let cap_eff = match read_cap_eff() {
Some(v) => v,
None => return Vec::new(),
};
FORBIDDEN
.iter()
.filter(|c| cap_eff & (1u64 << c.value) != 0)
.map(|c| c.name.to_string())
.collect()
}
#[cfg(target_os = "linux")]
fn read_cap_eff() -> Option<u64> {
let status = std::fs::read_to_string("/proc/self/status").ok()?;
for line in status.lines() {
if let Some(hex) = line.strip_prefix("CapEff:") {
return u64::from_str_radix(hex.trim(), 16).ok();
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unprivileged_process_passes_self_check() {
assert!(enforce_least_privilege().is_ok());
}
#[test]
fn privilege_error_lists_held_caps() {
let err = PrivilegeError {
held: vec!["CAP_BPF".to_string()],
};
assert!(err.to_string().contains("CAP_BPF"));
}
}