#![cfg_attr(docsrs, feature(doc_cfg))]
use std::ffi::{CStr, CString, OsStr, OsString};
use std::os::unix::prelude::*;
use std::ptr::NonNull;
mod arch;
mod err;
mod sys;
pub use arch::*;
pub use err::*;
#[cfg(feature = "libseccomp-2-5")]
mod notify;
#[cfg(feature = "libseccomp-2-5")]
pub use notify::*;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
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)]
#[non_exhaustive]
#[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)]
#[non_exhaustive]
#[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) -> Result<Self> {
match NonNull::new(unsafe { sys::seccomp_init(def_action.to_raw()) }) {
Some(ctx) => Ok(Self { ctx }),
None => Err(Error::new(libc::EINVAL)),
}
}
#[inline]
pub fn reset(&mut self, def_action: Action) -> Result<()> {
Error::unpack(unsafe { sys::seccomp_reset(self.ctx.as_ptr(), def_action.to_raw()) })?;
Ok(())
}
#[inline]
pub fn merge(&mut self, other: Self) -> Result<()> {
Error::unpack(unsafe { sys::seccomp_merge(self.ctx.as_ptr(), other.ctx.as_ptr()) })?;
std::mem::forget(other);
Ok(())
}
#[inline]
pub fn load(&mut self) -> Result<()> {
Error::unpack(unsafe { sys::seccomp_load(self.ctx.as_ptr()) })?;
Ok(())
}
#[inline]
pub fn export_bpf(&self, fd: RawFd) -> Result<()> {
Error::unpack(unsafe { sys::seccomp_export_bpf(self.ctx.as_ptr(), fd) })?;
Ok(())
}
#[inline]
pub fn export_pfc(&self, fd: RawFd) -> Result<()> {
Error::unpack(unsafe { sys::seccomp_export_pfc(self.ctx.as_ptr(), fd) })?;
Ok(())
}
#[inline]
pub fn add_arch(&mut self, arch: Arch) -> Result<()> {
Error::unpack(unsafe { sys::seccomp_arch_add(self.ctx.as_ptr(), arch as u32) })?;
Ok(())
}
#[inline]
pub fn remove_arch(&mut self, arch: Arch) -> Result<()> {
Error::unpack_enoent(unsafe { sys::seccomp_arch_remove(self.ctx.as_ptr(), arch as u32) })?;
Ok(())
}
pub fn has_arch(&self, arch: Arch) -> Result<bool> {
let res = unsafe { sys::seccomp_arch_exist(self.ctx.as_ptr(), arch as u32) };
if res == -libc::EEXIST {
Ok(false)
} else {
Error::unpack(res)?;
Ok(true)
}
}
#[inline]
pub fn syscall_priority(&mut self, syscall: libc::c_int, priority: u8) -> Result<()> {
Error::unpack(unsafe {
sys::seccomp_syscall_priority(self.ctx.as_ptr(), syscall, priority)
})?;
Ok(())
}
#[inline]
pub fn add_rule(&mut self, action: Action, syscall: libc::c_int, args: &[Arg]) -> Result<()> {
Error::unpack(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,
)
})?;
Ok(())
}
#[inline]
pub fn add_rule_exact(
&mut self,
action: Action,
syscall: libc::c_int,
args: &[Arg],
) -> Result<()> {
Error::unpack(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,
)
})?;
Ok(())
}
#[inline]
fn get_attr(&self, attr: libc::c_int) -> Result<u32> {
let mut res = 0;
Error::unpack_enoent(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) -> Result<()> {
Error::unpack_enoent(unsafe { sys::seccomp_attr_set(self.ctx.as_ptr(), attr, value) })?;
Ok(())
}
#[inline]
pub fn get_default_action(&self) -> Result<Action> {
Action::from_raw(self.get_attr(sys::SCMP_FLTATR_ACT_DEFAULT)?)
.ok_or_else(|| Error::new(libc::EINVAL))
}
#[inline]
pub fn get_badarch_action(&self) -> Result<Action> {
Action::from_raw(self.get_attr(sys::SCMP_FLTATR_ACT_BADARCH)?)
.ok_or_else(|| Error::new(libc::EINVAL))
}
#[inline]
pub fn set_badarch_action(&mut self, act: Action) -> Result<()> {
self.set_attr(sys::SCMP_FLTATR_ACT_BADARCH, act.to_raw())
}
#[inline]
pub fn get_flag(&self, flag: Flag) -> Result<bool> {
Ok(self.get_attr(flag as libc::c_int)? != 0)
}
#[inline]
pub fn set_flag(&mut self, flag: Flag, val: bool) -> Result<()> {
self.set_attr(flag as libc::c_int, val as u32)
}
#[inline]
pub fn get_optimize_level(&self) -> Result<u32> {
self.get_attr(sys::SCMP_FLTATR_CTL_OPTIMIZE)
}
#[inline]
pub fn set_optimize_level(&mut self, level: u32) -> Result<()> {
self.set_attr(sys::SCMP_FLTATR_CTL_OPTIMIZE, level)
}
#[cfg(feature = "libseccomp-2-5")]
#[cfg_attr(docsrs, doc(cfg(feature = "libseccomp-2-5")))]
pub fn get_notify_fd(&self) -> Result<RawFd> {
Error::unpack(unsafe { sys::seccomp_notify_fd(self.ctx.as_ptr()) })
}
#[cfg(feature = "libseccomp-2-5")]
#[cfg_attr(docsrs, doc(cfg(feature = "libseccomp-2-5")))]
pub fn receive_notify(&self) -> Result<Notification> {
Notification::receive(self.get_notify_fd()?)
}
#[cfg(feature = "libseccomp-2-5")]
#[cfg_attr(docsrs, doc(cfg(feature = "libseccomp-2-5")))]
pub fn respond_notify(&self, response: &mut NotificationResponse) -> Result<()> {
response.send_response(self.get_notify_fd()?)
}
}
impl Drop for Filter {
#[inline]
fn drop(&mut self) {
unsafe {
sys::seccomp_release(self.ctx.as_ptr());
}
}
}
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")]
#[cfg_attr(docsrs, doc(cfg(feature = "libseccomp-2-4")))]
#[inline]
pub fn api_get() -> libc::c_uint {
unsafe { sys::seccomp_api_get() }
}
#[cfg(feature = "libseccomp-2-4")]
#[cfg_attr(docsrs, doc(cfg(feature = "libseccomp-2-4")))]
#[inline]
pub fn api_set(level: libc::c_uint) -> Result<()> {
Error::unpack(unsafe { sys::seccomp_api_set(level) })?;
Ok(())
}
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() -> Result<()> {
Error::unpack(unsafe { sys::seccomp_reset(std::ptr::null_mut(), 0) })?;
Ok(())
}
#[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_action_from_raw() {
assert_eq!(
Action::from_raw(sys::SCMP_ACT_KILL_PROCESS | sys::SCMP_ACT_ALLOW),
None
);
}
#[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_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));
}
}