use std::ffi::{CStr, CString, OsStr, OsString};
use std::io;
use std::os::unix::prelude::*;
use std::ptr::NonNull;
mod arch;
mod sys;
pub use arch::{Arch, ParseArchError};
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum Action {
KillProcess,
KillThread,
Trap,
Notify,
Log,
Allow,
Errno(libc::c_int),
Trace(u16),
}
impl Action {
fn to_raw(self) -> u32 {
match self {
Self::KillProcess => sys::SCMP_ACT_KILL_PROCESS,
Self::KillThread => sys::SCMP_ACT_KILL,
Self::Trap => sys::SCMP_ACT_TRAP,
Self::Notify => sys::SCMP_ACT_NOTIFY,
Self::Log => sys::SCMP_ACT_LOG,
Self::Allow => sys::SCMP_ACT_ALLOW,
Self::Errno(eno) => sys::SCMP_ACT_ERRNO(eno as u16),
Self::Trace(msg_num) => sys::SCMP_ACT_TRACE(msg_num),
}
}
fn from_raw(val: u32) -> Option<Self> {
match val & sys::SCMP_ACT_MASK {
sys::SCMP_ACT_KILL_PROCESS => Some(Self::KillProcess),
sys::SCMP_ACT_KILL => Some(Self::KillThread),
sys::SCMP_ACT_TRAP => Some(Self::Trap),
sys::SCMP_ACT_NOTIFY => Some(Self::Notify),
sys::SCMP_ACT_LOG => Some(Self::Log),
sys::SCMP_ACT_ALLOW => Some(Self::Allow),
sys::SCMP_ACT_ERRNO_MASK => Some(Self::Errno(val as u16 as libc::c_int)),
sys::SCMP_ACT_TRACE_MASK => Some(Self::Trace(val as u16)),
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[repr(i32)]
pub enum Cmp {
Ne = sys::SCMP_CMP_NE,
Lt = sys::SCMP_CMP_LT,
Le = sys::SCMP_CMP_LE,
Eq = sys::SCMP_CMP_EQ,
Ge = sys::SCMP_CMP_GE,
Gt = sys::SCMP_CMP_GT,
MaskedEq = sys::SCMP_CMP_MASKED_EQ,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[repr(i32)]
pub enum Flag {
NoNewPrivs = sys::SCMP_FLTATR_CTL_NNP,
Tsync = sys::SCMP_FLTATR_CTL_TSYNC,
Tskip = sys::SCMP_FLTATR_API_TSKIP,
Log = sys::SCMP_FLTATR_CTL_LOG,
DisableSSB = sys::SCMP_FLTATR_CTL_SSB,
SysRawRC = sys::SCMP_FLTATR_API_SYSRAWRC,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct Arg {
arg: libc::c_uint,
op: Cmp,
data_a: u64,
data_b: u64,
}
impl Arg {
#[inline]
pub fn new(arg: libc::c_uint, op: Cmp, data_a: u64, data_b: u64) -> Self {
Self {
arg,
op,
data_a,
data_b,
}
}
#[inline]
pub fn new_ne(arg: libc::c_uint, data: u64) -> Self {
Self::new(arg, Cmp::Ne, data, 0)
}
#[inline]
pub fn new_lt(arg: libc::c_uint, data: u64) -> Self {
Self::new(arg, Cmp::Lt, data, 0)
}
#[inline]
#[inline]
pub fn new_le(arg: libc::c_uint, data: u64) -> Self {
Self::new(arg, Cmp::Le, data, 0)
}
#[inline]
pub fn new_eq(arg: libc::c_uint, data: u64) -> Self {
Self::new(arg, Cmp::Eq, data, 0)
}
#[inline]
pub fn new_ge(arg: libc::c_uint, data: u64) -> Self {
Self::new(arg, Cmp::Ge, data, 0)
}
#[inline]
pub fn new_gt(arg: libc::c_uint, data: u64) -> Self {
Self::new(arg, Cmp::Gt, data, 0)
}
#[inline]
pub fn new_masked_eq(arg: libc::c_uint, mask: u64, data: u64) -> Self {
Self::new(arg, Cmp::MaskedEq, mask, data)
}
}
#[derive(Debug)]
pub struct Filter {
ctx: NonNull<libc::c_void>,
}
impl Filter {
#[inline]
pub fn new(def_action: Action) -> io::Result<Self> {
match NonNull::new(unsafe { sys::seccomp_init(def_action.to_raw()) }) {
Some(ctx) => Ok(Self { ctx }),
None => Err(io::Error::from_raw_os_error(libc::EINVAL)),
}
}
#[inline]
pub fn reset(&mut self, def_action: Action) -> io::Result<()> {
check_status(unsafe { sys::seccomp_reset(self.ctx.as_ptr(), def_action.to_raw()) })
}
#[inline]
pub fn merge(&mut self, other: Self) -> io::Result<()> {
check_status(unsafe { sys::seccomp_merge(self.ctx.as_ptr(), other.ctx.as_ptr()) })?;
std::mem::forget(other);
Ok(())
}
#[inline]
pub fn load(&mut self) -> io::Result<()> {
check_status(unsafe { sys::seccomp_load(self.ctx.as_ptr()) })
}
#[inline]
pub fn export_bpf(&self, fd: RawFd) -> io::Result<()> {
check_status(unsafe { sys::seccomp_export_bpf(self.ctx.as_ptr(), fd) })
}
#[inline]
pub fn export_pfc(&self, fd: RawFd) -> io::Result<()> {
check_status(unsafe { sys::seccomp_export_pfc(self.ctx.as_ptr(), fd) })
}
#[inline]
pub fn add_arch(&mut self, arch: Arch) -> io::Result<()> {
check_status(unsafe { sys::seccomp_arch_add(self.ctx.as_ptr(), arch as u32) })
}
#[inline]
pub fn remove_arch(&mut self, arch: Arch) -> io::Result<()> {
match -unsafe { sys::seccomp_arch_remove(self.ctx.as_ptr(), arch as u32) } {
0 => Ok(()),
libc::EEXIST => Err(io::Error::from_raw_os_error(libc::ENOENT)),
ret => {
debug_assert!(ret > 0);
Err(io::Error::from_raw_os_error(ret))
}
}
}
pub fn has_arch(&self, arch: Arch) -> io::Result<bool> {
match -unsafe { sys::seccomp_arch_exist(self.ctx.as_ptr(), arch as u32) } {
0 => Ok(true),
libc::EEXIST => Ok(false),
ret => {
debug_assert!(ret > 0);
Err(io::Error::from_raw_os_error(ret))
}
}
}
#[inline]
pub fn syscall_priority(&mut self, syscall: libc::c_int, priority: u8) -> io::Result<()> {
check_status(unsafe { sys::seccomp_syscall_priority(self.ctx.as_ptr(), syscall, priority) })
}
#[inline]
pub fn add_rule(
&mut self,
action: Action,
syscall: libc::c_int,
args: &[Arg],
) -> io::Result<()> {
check_status(unsafe {
sys::seccomp_rule_add_array(
self.ctx.as_ptr(),
action.to_raw(),
syscall,
args.len() as libc::c_uint,
args.as_ptr() as *const sys::scmp_arg_cmp,
)
})
}
#[inline]
pub fn add_rule_exact(
&mut self,
action: Action,
syscall: libc::c_int,
args: &[Arg],
) -> io::Result<()> {
check_status(unsafe {
sys::seccomp_rule_add_exact_array(
self.ctx.as_ptr(),
action.to_raw(),
syscall,
args.len() as libc::c_uint,
args.as_ptr() as *const sys::scmp_arg_cmp,
)
})
}
#[inline]
fn get_attr(&mut self, attr: libc::c_int) -> io::Result<u32> {
let mut res = 0;
check_status(unsafe { sys::seccomp_attr_get(self.ctx.as_ptr(), attr, &mut res) })?;
Ok(res)
}
#[inline]
fn set_attr(&mut self, attr: libc::c_int, value: u32) -> io::Result<()> {
check_status(unsafe { sys::seccomp_attr_set(self.ctx.as_ptr(), attr, value) })
}
#[inline]
pub fn get_default_action(&mut self) -> io::Result<Action> {
Action::from_raw(self.get_attr(sys::SCMP_FLTATR_ACT_DEFAULT)?)
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))
}
#[inline]
pub fn get_badarch_action(&mut self) -> io::Result<Action> {
Action::from_raw(self.get_attr(sys::SCMP_FLTATR_ACT_BADARCH)?)
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))
}
#[inline]
pub fn set_badarch_action(&mut self, act: Action) -> io::Result<()> {
self.set_attr(sys::SCMP_FLTATR_ACT_BADARCH, act.to_raw())
}
#[inline]
pub fn get_flag(&mut self, flag: Flag) -> io::Result<bool> {
Ok(self.get_attr(flag as libc::c_int)? != 0)
}
#[inline]
pub fn set_flag(&mut self, flag: Flag, val: bool) -> io::Result<()> {
self.set_attr(flag as libc::c_int, val as u32)
}
#[inline]
pub fn get_optimize_level(&mut self) -> io::Result<u32> {
self.get_attr(sys::SCMP_FLTATR_CTL_OPTIMIZE)
}
#[inline]
pub fn set_optimize_level(&mut self, level: u32) -> io::Result<()> {
self.set_attr(sys::SCMP_FLTATR_CTL_OPTIMIZE, level)
}
}
impl Drop for Filter {
#[inline]
fn drop(&mut self) {
unsafe {
sys::seccomp_release(self.ctx.as_ptr());
}
}
}
fn check_status(ret: libc::c_int) -> io::Result<()> {
if ret == 0 {
Ok(())
} else {
debug_assert!(ret < 0);
Err(io::Error::from_raw_os_error(-ret))
}
}
pub fn resolve_syscall_num(arch: Arch, num: libc::c_int) -> Option<OsString> {
let ptr = unsafe { sys::seccomp_syscall_resolve_num_arch(arch as u32, num) };
if ptr.is_null() {
return None;
}
let s = OsStr::from_bytes(unsafe { CStr::from_ptr(ptr) }.to_bytes()).to_os_string();
unsafe {
libc::free(ptr as *mut libc::c_void);
}
Some(s)
}
#[inline]
pub fn resolve_syscall_name<N: AsRef<OsStr>>(name: N) -> Option<libc::c_int> {
resolve_syscall_name_arch(Arch::NATIVE, name)
}
#[inline]
pub fn resolve_syscall_name_arch<N: AsRef<OsStr>>(arch: Arch, name: N) -> Option<libc::c_int> {
fn inner(arch: Arch, name: &OsStr) -> Option<libc::c_int> {
let c_name = CString::new(name.as_bytes()).ok()?;
match unsafe { sys::seccomp_syscall_resolve_name_arch(arch as u32, c_name.as_ptr()) } {
sys::NR_SCMP_ERROR => None,
nr => Some(nr),
}
}
inner(arch, name.as_ref())
}
#[inline]
pub fn resolve_syscall_name_rewrite<N: AsRef<OsStr>>(arch: Arch, name: N) -> Option<libc::c_int> {
fn inner(arch: Arch, name: &OsStr) -> Option<libc::c_int> {
let c_name = CString::new(name.as_bytes()).ok()?;
match unsafe { sys::seccomp_syscall_resolve_name_rewrite(arch as u32, c_name.as_ptr()) } {
sys::NR_SCMP_ERROR => None,
nr => Some(nr),
}
}
inner(arch, name.as_ref())
}
#[cfg(feature = "libseccomp-2-4")]
#[inline]
pub fn api_get() -> libc::c_uint {
unsafe { sys::seccomp_api_get() }
}
#[cfg(feature = "libseccomp-2-4")]
#[inline]
pub fn api_set(level: libc::c_uint) -> io::Result<()> {
check_status(unsafe { sys::seccomp_api_set(level) })
}
pub fn libseccomp_version() -> (libc::c_uint, libc::c_uint, libc::c_uint) {
let ver = unsafe { sys::seccomp_version().as_ref().unwrap() };
(ver.major, ver.minor, ver.micro)
}
pub fn reset_global_state() -> io::Result<()> {
check_status(unsafe { sys::seccomp_reset(std::ptr::null_mut(), 0) })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arg_new() {
assert_eq!(Arg::new(1, Cmp::Ne, 2, 0), Arg::new_ne(1, 2));
assert_eq!(Arg::new(1, Cmp::Lt, 2, 0), Arg::new_lt(1, 2));
assert_eq!(Arg::new(1, Cmp::Le, 2, 0), Arg::new_le(1, 2));
assert_eq!(Arg::new(1, Cmp::Eq, 2, 0), Arg::new_eq(1, 2));
assert_eq!(Arg::new(1, Cmp::Ge, 2, 0), Arg::new_ge(1, 2));
assert_eq!(Arg::new(1, Cmp::Gt, 2, 0), Arg::new_gt(1, 2));
assert_eq!(
Arg::new(1, Cmp::MaskedEq, !0o777, 0),
Arg::new_masked_eq(1, !0o777, 0)
);
}
#[test]
fn test_resolve_syscall() {
assert_eq!(
resolve_syscall_num(Arch::NATIVE, resolve_syscall_name("read").unwrap()).unwrap(),
"read"
);
assert_eq!(
resolve_syscall_name("read").unwrap(),
resolve_syscall_name_rewrite(Arch::NATIVE, "read").unwrap(),
);
assert_eq!(resolve_syscall_name("NOSYSCALL"), None);
assert_eq!(resolve_syscall_name("read\0"), None);
assert_eq!(
resolve_syscall_name_rewrite(Arch::NATIVE, "NOSYSCALL"),
None
);
assert_eq!(resolve_syscall_num(Arch::NATIVE, -1), None);
assert_eq!(
resolve_syscall_name_rewrite(Arch::X86, "socketcall").unwrap(),
resolve_syscall_name_rewrite(Arch::X86, "socket").unwrap(),
);
}
#[test]
fn test_check_status() {
check_status(0).unwrap();
assert_eq!(
check_status(-libc::EEXIST).unwrap_err().raw_os_error(),
Some(libc::EEXIST)
);
}
#[test]
fn test_version() {
assert!(libseccomp_version() >= (2, 3, 0));
#[cfg(feature = "libseccomp-2-4")]
assert!(libseccomp_version() >= (2, 4, 0));
#[cfg(feature = "libseccomp-2-5")]
assert!(libseccomp_version() >= (2, 5, 0));
}
}