#[cfg(target_os = "linux")]
use nix::sched::{CloneFlags, unshare};
use crate::policy::SandboxPolicy;
#[derive(Debug, Clone)]
pub struct NamespaceConfig {
pub new_pid: bool,
pub new_mount: bool,
pub new_net: bool,
pub new_user: bool,
pub host_uid: u32,
pub host_gid: u32,
}
impl NamespaceConfig {
pub fn from_policy(policy: &SandboxPolicy) -> Self {
Self {
new_pid: true, new_mount: true, new_net: !policy.network.enabled, new_user: true, host_uid: unsafe { libc::getuid() },
host_gid: unsafe { libc::getgid() },
}
}
#[inline]
#[must_use]
pub fn any_enabled(&self) -> bool {
self.new_pid || self.new_mount || self.new_net || self.new_user
}
#[cfg(target_os = "linux")]
#[must_use]
pub fn clone_flags(&self) -> CloneFlags {
let mut flags = CloneFlags::empty();
if self.new_pid {
flags |= CloneFlags::CLONE_NEWPID;
}
if self.new_mount {
flags |= CloneFlags::CLONE_NEWNS;
}
if self.new_net {
flags |= CloneFlags::CLONE_NEWNET;
}
if self.new_user {
flags |= CloneFlags::CLONE_NEWUSER;
}
flags
}
}
#[cfg(target_os = "linux")]
pub fn apply_namespaces(config: &NamespaceConfig) -> crate::Result<()> {
if !config.any_enabled() {
return Ok(());
}
let flags = config.clone_flags();
unshare(flags)
.map_err(|e| crate::KavachError::ExecFailed(format!("namespace unshare failed: {e}")))?;
if config.new_user {
let uid = config.host_uid;
let gid = config.host_gid;
if let Err(_e) = write_id_maps(uid, gid) {
}
}
Ok(())
}
#[cfg(target_os = "linux")]
fn write_id_maps(host_uid: u32, host_gid: u32) -> std::io::Result<()> {
use std::io::Write;
let mut f = std::fs::File::create("/proc/self/setgroups")?;
f.write_all(b"deny\n")?;
let mut f = std::fs::File::create("/proc/self/uid_map")?;
writeln!(f, "0 {host_uid} 1")?;
let mut f = std::fs::File::create("/proc/self/gid_map")?;
writeln!(f, "0 {host_gid} 1")?;
Ok(())
}
#[cfg(target_os = "linux")]
pub fn drop_capabilities() -> crate::Result<()> {
use caps::{CapSet, Capability};
let dangerous = [
Capability::CAP_SYS_ADMIN,
Capability::CAP_SYS_PTRACE,
Capability::CAP_NET_RAW,
Capability::CAP_NET_ADMIN,
Capability::CAP_SYS_MODULE,
Capability::CAP_SYS_RAWIO,
Capability::CAP_SYS_BOOT,
Capability::CAP_SYS_CHROOT,
Capability::CAP_MKNOD,
];
for cap in &dangerous {
let _ = caps::drop(None, CapSet::Effective, *cap);
let _ = caps::drop(None, CapSet::Permitted, *cap);
let _ = caps::drop(None, CapSet::Inheritable, *cap);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_from_policy_network_disabled() {
let mut policy = SandboxPolicy::minimal();
policy.network.enabled = false;
let config = NamespaceConfig::from_policy(&policy);
assert!(config.new_pid);
assert!(config.new_mount);
assert!(config.new_net); assert!(config.new_user);
}
#[test]
fn config_from_policy_network_enabled() {
let mut policy = SandboxPolicy::minimal();
policy.network.enabled = true;
let config = NamespaceConfig::from_policy(&policy);
assert!(!config.new_net); }
#[test]
fn any_enabled_default() {
let policy = SandboxPolicy::minimal();
let config = NamespaceConfig::from_policy(&policy);
assert!(config.any_enabled());
}
#[test]
fn any_enabled_none() {
let config = NamespaceConfig {
new_pid: false,
new_mount: false,
new_net: false,
new_user: false,
host_uid: 1000,
host_gid: 1000,
};
assert!(!config.any_enabled());
}
#[cfg(target_os = "linux")]
#[test]
fn clone_flags_all() {
let config = NamespaceConfig {
new_pid: true,
new_mount: true,
new_net: true,
new_user: true,
host_uid: 1000,
host_gid: 1000,
};
let flags = config.clone_flags();
assert!(flags.contains(CloneFlags::CLONE_NEWPID));
assert!(flags.contains(CloneFlags::CLONE_NEWNS));
assert!(flags.contains(CloneFlags::CLONE_NEWNET));
assert!(flags.contains(CloneFlags::CLONE_NEWUSER));
}
#[test]
fn from_policy_captures_uid_gid() {
let policy = SandboxPolicy::minimal();
let config = NamespaceConfig::from_policy(&policy);
let _ = config.host_uid;
let _ = config.host_gid;
assert!(config.new_user);
}
#[cfg(target_os = "linux")]
#[test]
fn clone_flags_partial() {
let config = NamespaceConfig {
new_pid: true,
new_mount: false,
new_net: false,
new_user: true,
host_uid: 1000,
host_gid: 1000,
};
let flags = config.clone_flags();
assert!(flags.contains(CloneFlags::CLONE_NEWPID));
assert!(!flags.contains(CloneFlags::CLONE_NEWNS));
assert!(!flags.contains(CloneFlags::CLONE_NEWNET));
assert!(flags.contains(CloneFlags::CLONE_NEWUSER));
}
}