pub mod error;
#[cfg(any(libseccomp_v2_5, docsrs))]
pub mod notify;
use error::ErrorKind::*;
use error::{Result, SeccompError};
use libseccomp_sys::*;
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::unix::io::AsRawFd;
use std::ptr::NonNull;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScmpVersion {
pub major: u32,
pub minor: u32,
pub micro: u32,
}
impl ScmpVersion {
pub fn current() -> Result<Self> {
if let Some(version) = unsafe { seccomp_version().as_ref() } {
Ok(Self {
major: version.major,
minor: version.minor,
micro: version.micro,
})
} else {
Err(SeccompError::new(Common(
"Could not get seccomp version".to_string(),
)))
}
}
}
impl From<(u32, u32, u32)> for ScmpVersion {
fn from(version: (u32, u32, u32)) -> Self {
Self {
major: version.0,
minor: version.1,
micro: version.2,
}
}
}
impl fmt::Display for ScmpVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.micro)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ScmpFilterAttr {
ActDefault,
ActBadArch,
CtlNnp,
CtlTsync,
ApiTskip,
CtlLog,
CtlSsb,
CtlOptimize,
ApiSysRawRc,
}
impl ScmpFilterAttr {
fn to_sys(self) -> scmp_filter_attr {
match self {
Self::ActDefault => scmp_filter_attr::SCMP_FLTATR_ACT_DEFAULT,
Self::ActBadArch => scmp_filter_attr::SCMP_FLTATR_ACT_BADARCH,
Self::CtlNnp => scmp_filter_attr::SCMP_FLTATR_CTL_NNP,
Self::CtlTsync => scmp_filter_attr::SCMP_FLTATR_CTL_TSYNC,
Self::ApiTskip => scmp_filter_attr::SCMP_FLTATR_API_TSKIP,
Self::CtlLog => scmp_filter_attr::SCMP_FLTATR_CTL_LOG,
Self::CtlSsb => scmp_filter_attr::SCMP_FLTATR_CTL_SSB,
Self::CtlOptimize => scmp_filter_attr::SCMP_FLTATR_CTL_OPTIMIZE,
Self::ApiSysRawRc => scmp_filter_attr::SCMP_FLTATR_API_SYSRAWRC,
}
}
}
impl std::str::FromStr for ScmpFilterAttr {
type Err = SeccompError;
fn from_str(attr: &str) -> Result<Self> {
match attr {
"SCMP_FLTATR_ACT_DEFAULT" => Ok(Self::ActDefault),
"SCMP_FLTATR_ACT_BADARCH" => Ok(Self::ActBadArch),
"SCMP_FLTATR_CTL_NNP" => Ok(Self::CtlNnp),
"SCMP_FLTATR_CTL_TSYNC" => Ok(Self::CtlTsync),
"SCMP_FLTATR_API_TSKIP" => Ok(Self::ApiTskip),
"SCMP_FLTATR_CTL_LOG" => Ok(Self::CtlLog),
"SCMP_FLTATR_CTL_SSB" => Ok(Self::CtlSsb),
"SCMP_FLTATR_CTL_OPTIMIZE" => Ok(Self::CtlOptimize),
"SCMP_FLTATR_API_SYSRAWRC" => Ok(Self::ApiSysRawRc),
_ => Err(SeccompError::new(ParseError)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ScmpCompareOp {
NotEqual,
Less,
LessOrEqual,
Equal,
GreaterEqual,
Greater,
MaskedEqual(#[doc = "mask"] u64),
}
impl ScmpCompareOp {
const fn to_sys(self) -> scmp_compare {
match self {
Self::NotEqual => scmp_compare::SCMP_CMP_NE,
Self::Less => scmp_compare::SCMP_CMP_LT,
Self::LessOrEqual => scmp_compare::SCMP_CMP_LE,
Self::Equal => scmp_compare::SCMP_CMP_EQ,
Self::GreaterEqual => scmp_compare::SCMP_CMP_GE,
Self::Greater => scmp_compare::SCMP_CMP_GT,
Self::MaskedEqual(_) => scmp_compare::SCMP_CMP_MASKED_EQ,
}
}
}
impl std::str::FromStr for ScmpCompareOp {
type Err = SeccompError;
fn from_str(cmp_op: &str) -> Result<Self> {
match cmp_op {
"SCMP_CMP_NE" => Ok(Self::NotEqual),
"SCMP_CMP_LT" => Ok(Self::Less),
"SCMP_CMP_LE" => Ok(Self::LessOrEqual),
"SCMP_CMP_EQ" => Ok(Self::Equal),
"SCMP_CMP_GE" => Ok(Self::GreaterEqual),
"SCMP_CMP_GT" => Ok(Self::Greater),
"SCMP_CMP_MASKED_EQ" => Ok(Self::MaskedEqual(u64::default())),
_ => Err(SeccompError::new(ParseError)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(transparent)]
pub struct ScmpArgCompare(scmp_arg_cmp);
impl ScmpArgCompare {
pub const fn new(arg: u32, op: ScmpCompareOp, datum: u64) -> Self {
if let ScmpCompareOp::MaskedEqual(mask) = op {
Self(scmp_arg_cmp {
arg,
op: op.to_sys(),
datum_a: mask,
datum_b: datum,
})
} else {
Self(scmp_arg_cmp {
arg,
op: op.to_sys(),
datum_a: datum,
datum_b: 0,
})
}
}
}
impl From<ScmpArgCompare> for scmp_arg_cmp {
fn from(v: ScmpArgCompare) -> scmp_arg_cmp {
v.0
}
}
impl From<&ScmpArgCompare> for scmp_arg_cmp {
fn from(v: &ScmpArgCompare) -> scmp_arg_cmp {
v.0
}
}
#[rustfmt::skip]
#[doc(hidden)]
#[macro_export]
macro_rules! __private_scmp_cmp_arg {
(arg0) => { 0 };
(arg1) => { 1 };
(arg2) => { 2 };
(arg3) => { 3 };
(arg4) => { 4 };
(arg5) => { 5 };
}
#[macro_export]
macro_rules! scmp_cmp {
($_:tt $arg:tt != $datum:expr) => {
$crate::ScmpArgCompare::new(
$crate::__private_scmp_cmp_arg!($arg),
$crate::ScmpCompareOp::NotEqual,
$datum,
)
};
($_:tt $arg:tt < $datum:expr) => {
$crate::ScmpArgCompare::new(
$crate::__private_scmp_cmp_arg!($arg),
$crate::ScmpCompareOp::Less,
$datum,
)
};
($_:tt $arg:tt <= $datum:expr) => {
$crate::ScmpArgCompare::new(
$crate::__private_scmp_cmp_arg!($arg),
$crate::ScmpCompareOp::LessOrEqual,
$datum,
)
};
($_:tt $arg:tt == $datum:expr) => {
$crate::ScmpArgCompare::new(
$crate::__private_scmp_cmp_arg!($arg),
$crate::ScmpCompareOp::Equal,
$datum,
)
};
($_:tt $arg:tt >= $datum:expr) => {
$crate::ScmpArgCompare::new(
$crate::__private_scmp_cmp_arg!($arg),
$crate::ScmpCompareOp::GreaterEqual,
$datum,
)
};
($_:tt $arg:tt > $datum:expr) => {
$crate::ScmpArgCompare::new(
$crate::__private_scmp_cmp_arg!($arg),
$crate::ScmpCompareOp::Greater,
$datum,
)
};
($_:tt $arg:tt & $mask:tt == $datum:expr) => {
$crate::ScmpArgCompare::new(
$crate::__private_scmp_cmp_arg!($arg),
$crate::ScmpCompareOp::MaskedEqual($mask),
$datum,
)
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ScmpAction {
KillProcess,
KillThread,
Trap,
Notify,
Errno(i32),
Trace(u16),
Log,
Allow,
}
impl ScmpAction {
fn to_sys(self) -> u32 {
match self {
Self::KillProcess => SCMP_ACT_KILL_PROCESS,
Self::KillThread => SCMP_ACT_KILL_THREAD,
Self::Trap => SCMP_ACT_TRAP,
Self::Notify => SCMP_ACT_NOTIFY,
Self::Errno(x) => SCMP_ACT_ERRNO(x as u16),
Self::Trace(x) => SCMP_ACT_TRACE(x),
Self::Log => SCMP_ACT_LOG,
Self::Allow => SCMP_ACT_ALLOW,
}
}
pub fn from_str(action: &str, val: Option<i32>) -> Result<Self> {
match action {
"SCMP_ACT_KILL_PROCESS" => Ok(Self::KillProcess),
"SCMP_ACT_KILL_THREAD" => Ok(Self::KillThread),
"SCMP_ACT_KILL" => Ok(Self::KillThread),
"SCMP_ACT_TRAP" => Ok(Self::Trap),
"SCMP_ACT_NOTIFY" => Ok(Self::Notify),
"SCMP_ACT_ERRNO" => match val {
Some(v) => Ok(Self::Errno(v)),
None => Err(SeccompError::new(ParseError)),
},
"SCMP_ACT_TRACE" => match val {
Some(v) => Ok(Self::Trace(v.try_into()?)),
None => Err(SeccompError::new(ParseError)),
},
"SCMP_ACT_LOG" => Ok(Self::Log),
"SCMP_ACT_ALLOW" => Ok(Self::Allow),
_ => Err(SeccompError::new(ParseError)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ScmpArch {
Native,
X86,
X8664,
X32,
Arm,
Aarch64,
Mips,
Mips64,
Mips64N32,
Mipsel,
Mipsel64,
Mipsel64N32,
Ppc,
Ppc64,
Ppc64Le,
S390,
S390X,
Parisc,
Parisc64,
Riscv64,
}
impl ScmpArch {
fn to_sys(self) -> u32 {
match self {
Self::Native => SCMP_ARCH_NATIVE,
Self::X86 => SCMP_ARCH_X86,
Self::X8664 => SCMP_ARCH_X86_64,
Self::X32 => SCMP_ARCH_X32,
Self::Arm => SCMP_ARCH_ARM,
Self::Aarch64 => SCMP_ARCH_AARCH64,
Self::Mips => SCMP_ARCH_MIPS,
Self::Mips64 => SCMP_ARCH_MIPS64,
Self::Mips64N32 => SCMP_ARCH_MIPS64N32,
Self::Mipsel => SCMP_ARCH_MIPSEL,
Self::Mipsel64 => SCMP_ARCH_MIPSEL64,
Self::Mipsel64N32 => SCMP_ARCH_MIPSEL64N32,
Self::Ppc => SCMP_ARCH_PPC,
Self::Ppc64 => SCMP_ARCH_PPC64,
Self::Ppc64Le => SCMP_ARCH_PPC64LE,
Self::S390 => SCMP_ARCH_S390,
Self::S390X => SCMP_ARCH_S390X,
Self::Parisc => SCMP_ARCH_PARISC,
Self::Parisc64 => SCMP_ARCH_PARISC64,
Self::Riscv64 => SCMP_ARCH_RISCV64,
}
}
fn from_sys(arch: u32) -> Result<Self> {
match arch {
SCMP_ARCH_NATIVE => Ok(Self::Native),
SCMP_ARCH_X86 => Ok(Self::X86),
SCMP_ARCH_X86_64 => Ok(Self::X8664),
SCMP_ARCH_X32 => Ok(Self::X32),
SCMP_ARCH_ARM => Ok(Self::Arm),
SCMP_ARCH_AARCH64 => Ok(Self::Aarch64),
SCMP_ARCH_MIPS => Ok(Self::Mips),
SCMP_ARCH_MIPS64 => Ok(Self::Mips64),
SCMP_ARCH_MIPS64N32 => Ok(Self::Mips64N32),
SCMP_ARCH_MIPSEL => Ok(Self::Mipsel),
SCMP_ARCH_MIPSEL64 => Ok(Self::Mipsel64),
SCMP_ARCH_MIPSEL64N32 => Ok(Self::Mipsel64N32),
SCMP_ARCH_PPC => Ok(Self::Ppc),
SCMP_ARCH_PPC64 => Ok(Self::Ppc64),
SCMP_ARCH_PPC64LE => Ok(Self::Ppc64Le),
SCMP_ARCH_S390 => Ok(Self::S390),
SCMP_ARCH_S390X => Ok(Self::S390X),
SCMP_ARCH_PARISC => Ok(Self::Parisc),
SCMP_ARCH_PARISC64 => Ok(Self::Parisc64),
SCMP_ARCH_RISCV64 => Ok(Self::Riscv64),
_ => Err(SeccompError::new(ParseError)),
}
}
pub fn native() -> Result<Self> {
let ret = unsafe { seccomp_arch_native() };
match Self::from_sys(ret) {
Ok(v) => Ok(v),
Err(_) => Err(SeccompError::new(Common(
"Could not get native architecture".to_string(),
))),
}
}
}
impl std::str::FromStr for ScmpArch {
type Err = SeccompError;
fn from_str(arch: &str) -> Result<Self> {
match arch {
"SCMP_ARCH_NATIVE" => Ok(Self::Native),
"SCMP_ARCH_X86" => Ok(Self::X86),
"SCMP_ARCH_X86_64" => Ok(Self::X8664),
"SCMP_ARCH_X32" => Ok(Self::X32),
"SCMP_ARCH_ARM" => Ok(Self::Arm),
"SCMP_ARCH_AARCH64" => Ok(Self::Aarch64),
"SCMP_ARCH_MIPS" => Ok(Self::Mips),
"SCMP_ARCH_MIPS64" => Ok(Self::Mips64),
"SCMP_ARCH_MIPSEL" => Ok(Self::Mipsel),
"SCMP_ARCH_MIPSEL64" => Ok(Self::Mipsel64),
"SCMP_ARCH_MIPSEL64N32" => Ok(Self::Mipsel64N32),
"SCMP_ARCH_PPC" => Ok(Self::Ppc),
"SCMP_ARCH_PPC64" => Ok(Self::Ppc64),
"SCMP_ARCH_PPC64LE" => Ok(Self::Ppc64Le),
"SCMP_ARCH_S390" => Ok(Self::S390),
"SCMP_ARCH_S390X" => Ok(Self::S390X),
"SCMP_ARCH_PARISC" => Ok(Self::Parisc),
"SCMP_ARCH_PARISC64" => Ok(Self::Parisc64),
"SCMP_ARCH_RISCV64" => Ok(Self::Riscv64),
_ => Err(SeccompError::new(ParseError)),
}
}
}
#[derive(Debug)]
pub struct ScmpFilterContext {
ctx: NonNull<libc::c_void>,
}
impl ScmpFilterContext {
pub fn new_filter(default_action: ScmpAction) -> Result<ScmpFilterContext> {
let ctx_ptr = unsafe { seccomp_init(default_action.to_sys()) };
let ctx = NonNull::new(ctx_ptr)
.ok_or_else(|| SeccompError::new(Common("Could not create new filter".to_string())))?;
Ok(ScmpFilterContext { ctx })
}
pub fn merge(&mut self, src: Self) -> Result<()> {
let ret = unsafe { seccomp_merge(self.ctx.as_ptr(), src.ctx.as_ptr()) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
std::mem::forget(src);
Ok(())
}
pub fn is_arch_present(&self, arch: ScmpArch) -> Result<bool> {
let ret = unsafe { seccomp_arch_exist(self.ctx.as_ptr(), arch.to_sys()) };
if ret != 0 {
if ret == -(libc::EEXIST as i32) {
return Ok(false);
}
return Err(SeccompError::new(Errno(ret)));
}
Ok(true)
}
pub fn add_arch(&mut self, arch: ScmpArch) -> Result<()> {
let ret = unsafe { seccomp_arch_add(self.ctx.as_ptr(), arch.to_sys()) };
if ret != 0 && ret != -(libc::EEXIST as i32) {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn remove_arch(&mut self, arch: ScmpArch) -> Result<()> {
let ret = unsafe { seccomp_arch_remove(self.ctx.as_ptr(), arch.to_sys()) };
if ret != 0 && ret != -(libc::EEXIST as i32) {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn add_rule(&mut self, action: ScmpAction, syscall: i32) -> Result<()> {
self.add_rule_conditional(action, syscall, &[])
}
pub fn add_rule_conditional(
&mut self,
action: ScmpAction,
syscall: i32,
comparators: &[ScmpArgCompare],
) -> Result<()> {
let ret = unsafe {
seccomp_rule_add_array(
self.ctx.as_ptr(),
action.to_sys(),
syscall,
comparators.len() as u32,
comparators.as_ptr() as *const scmp_arg_cmp,
)
};
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn add_rule_exact(&mut self, action: ScmpAction, syscall: i32) -> Result<()> {
self.add_rule_conditional_exact(action, syscall, &[])
}
pub fn add_rule_conditional_exact(
&mut self,
action: ScmpAction,
syscall: i32,
comparators: &[ScmpArgCompare],
) -> Result<()> {
let ret = unsafe {
seccomp_rule_add_exact_array(
self.ctx.as_ptr(),
action.to_sys(),
syscall,
comparators.len() as u32,
comparators.as_ptr() as *const scmp_arg_cmp,
)
};
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn load(&self) -> Result<()> {
let ret = unsafe { seccomp_load(self.ctx.as_ptr()) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn set_syscall_priority(&mut self, syscall: i32, priority: u8) -> Result<()> {
let ret = unsafe { seccomp_syscall_priority(self.ctx.as_ptr(), syscall, priority) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn get_filter_attr(&self, attr: ScmpFilterAttr) -> Result<u32> {
let mut attribute: u32 = 0;
let ret = unsafe { seccomp_attr_get(self.ctx.as_ptr(), attr.to_sys(), &mut attribute) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(attribute)
}
pub fn get_no_new_privs_bit(&self) -> Result<bool> {
let ret = self.get_filter_attr(ScmpFilterAttr::CtlNnp)?;
Ok(ret != 0)
}
pub fn set_filter_attr(&mut self, attr: ScmpFilterAttr, value: u32) -> Result<()> {
let ret = unsafe { seccomp_attr_set(self.ctx.as_ptr(), attr.to_sys(), value) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn set_no_new_privs_bit(&mut self, state: bool) -> Result<()> {
self.set_filter_attr(ScmpFilterAttr::CtlNnp, state.into())
}
pub fn export_pfc<T: AsRawFd>(&self, fd: &mut T) -> Result<()> {
let ret = unsafe { seccomp_export_pfc(self.ctx.as_ptr(), fd.as_raw_fd()) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn export_bpf<T: AsRawFd>(&self, fd: &mut T) -> Result<()> {
let ret = unsafe { seccomp_export_bpf(self.ctx.as_ptr(), fd.as_raw_fd()) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn reset(&mut self, action: ScmpAction) -> Result<()> {
let ret = unsafe { seccomp_reset(self.ctx.as_ptr(), action.to_sys()) };
if ret != 0 {
return Err(SeccompError::new(Errno(ret)));
}
Ok(())
}
pub fn as_ptr(&self) -> scmp_filter_ctx {
self.ctx.as_ptr()
}
}
impl Drop for ScmpFilterContext {
fn drop(&mut self) {
unsafe { seccomp_release(self.ctx.as_ptr()) }
}
}
pub fn check_version(expected: ScmpVersion) -> Result<bool> {
let current = ScmpVersion::current()?;
if current.major == expected.major
&& (current.minor > expected.minor
|| (current.minor == expected.minor && current.micro >= expected.micro))
{
Ok(true)
} else {
Ok(false)
}
}
pub fn check_api(min_level: u32, expected: ScmpVersion) -> Result<bool> {
let level = get_api()?;
if level >= min_level && check_version(expected)? {
Ok(true)
} else {
Ok(false)
}
}
#[allow(dead_code)]
fn ensure_supported_version(msg: &str, expected: ScmpVersion) -> Result<()> {
if check_version(expected)? {
Ok(())
} else {
let current = ScmpVersion::current()?;
Err(SeccompError::new(Common(format!(
"{} requires libseccomp >= {} (current version: {})",
msg, expected, current,
))))
}
}
#[allow(dead_code)]
fn ensure_supported_api(msg: &str, min_level: u32, expected: ScmpVersion) -> Result<()> {
let level = get_api()?;
if level >= min_level {
ensure_supported_version(msg, expected)
} else {
let current = ScmpVersion::current()?;
Err(SeccompError::new(Common(format!(
"{} requires libseccomp >= {} and API level >= {} (current version: {}, API level: {})",
msg, expected, min_level, current, level
))))
}
}
#[deprecated(since = "0.2.0", note = "Use ScmpVersion::current().")]
pub fn get_library_version() -> Result<ScmpVersion> {
ScmpVersion::current()
}
#[deprecated(since = "0.2.0", note = "Use ScmpArch::native()")]
pub fn get_native_arch() -> Result<ScmpArch> {
ScmpArch::native()
}
pub fn get_api() -> Result<u32> {
let ret = unsafe { seccomp_api_get() };
if ret == 0 {
return Err(SeccompError::new(Common(
"API level operations are not supported".to_string(),
)));
}
Ok(ret)
}
pub fn set_api(level: u32) -> Result<()> {
let ret = unsafe { seccomp_api_set(level) };
if ret != 0 {
return Err(SeccompError::new(Common(
"API level operations are not supported".to_string(),
)));
}
Ok(())
}
pub fn get_syscall_name_from_arch(arch: ScmpArch, syscall: i32) -> Result<String> {
let ret = unsafe { seccomp_syscall_resolve_num_arch(arch.to_sys(), syscall) };
if ret.is_null() {
return Err(SeccompError::new(Common(format!(
"Could not resolve syscall number {}",
syscall
))));
}
let name = unsafe { CStr::from_ptr(ret) }.to_str()?.to_string();
unsafe { libc::free(ret as *mut libc::c_void) };
Ok(name)
}
pub fn get_syscall_from_name(name: &str, arch: Option<ScmpArch>) -> Result<i32> {
let name_c = CString::new(name)?;
let syscall: i32;
match arch {
Some(arch) => {
syscall = unsafe { seccomp_syscall_resolve_name_arch(arch.to_sys(), name_c.as_ptr()) };
}
None => {
syscall = unsafe { seccomp_syscall_resolve_name(name_c.as_ptr()) };
}
}
if syscall == __NR_SCMP_ERROR {
return Err(SeccompError::new(Common(format!(
"Could not resolve syscall name {}",
name
))));
}
Ok(syscall)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{stdout, Error};
use std::str::FromStr;
macro_rules! syscall_assert {
($e1: expr, $e2: expr) => {
let mut errno: i32 = 0;
if $e1 < 0 {
errno = -Error::last_os_error().raw_os_error().unwrap()
}
assert_eq!(errno, $e2);
};
}
#[test]
fn test_parse() {
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_ACT_DEFAULT")
.unwrap()
.to_sys(),
ScmpFilterAttr::ActDefault.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_ACT_BADARCH")
.unwrap()
.to_sys(),
ScmpFilterAttr::ActBadArch.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_CTL_NNP")
.unwrap()
.to_sys(),
ScmpFilterAttr::CtlNnp.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_CTL_TSYNC")
.unwrap()
.to_sys(),
ScmpFilterAttr::CtlTsync.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_API_TSKIP")
.unwrap()
.to_sys(),
ScmpFilterAttr::ApiTskip.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_CTL_LOG")
.unwrap()
.to_sys(),
ScmpFilterAttr::CtlLog.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_CTL_SSB")
.unwrap()
.to_sys(),
ScmpFilterAttr::CtlSsb.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_CTL_OPTIMIZE")
.unwrap()
.to_sys(),
ScmpFilterAttr::CtlOptimize.to_sys()
);
assert_eq!(
ScmpFilterAttr::from_str("SCMP_FLTATR_API_SYSRAWRC")
.unwrap()
.to_sys(),
ScmpFilterAttr::ApiSysRawRc.to_sys()
);
assert!(ScmpFilterAttr::from_str("SCMP_INVALID_FLAG").is_err());
assert_eq!(
ScmpCompareOp::from_str("SCMP_CMP_NE").unwrap().to_sys(),
ScmpCompareOp::NotEqual.to_sys()
);
assert_eq!(
ScmpCompareOp::from_str("SCMP_CMP_LT").unwrap().to_sys(),
ScmpCompareOp::Less.to_sys()
);
assert_eq!(
ScmpCompareOp::from_str("SCMP_CMP_LE").unwrap().to_sys(),
ScmpCompareOp::LessOrEqual.to_sys()
);
assert_eq!(
ScmpCompareOp::from_str("SCMP_CMP_EQ").unwrap().to_sys(),
ScmpCompareOp::Equal.to_sys()
);
assert_eq!(
ScmpCompareOp::from_str("SCMP_CMP_GE").unwrap().to_sys(),
ScmpCompareOp::GreaterEqual.to_sys()
);
assert_eq!(
ScmpCompareOp::from_str("SCMP_CMP_GT").unwrap().to_sys(),
ScmpCompareOp::Greater.to_sys()
);
assert_eq!(
ScmpCompareOp::from_str("SCMP_CMP_MASKED_EQ")
.unwrap()
.to_sys(),
ScmpCompareOp::MaskedEqual(u64::default()).to_sys()
);
assert!(ScmpCompareOp::from_str("SCMP_INVALID_FLAG").is_err());
assert_eq!(
ScmpAction::from_str("SCMP_ACT_KILL_PROCESS", None)
.unwrap()
.to_sys(),
ScmpAction::KillProcess.to_sys()
);
assert_eq!(
ScmpAction::from_str("SCMP_ACT_ERRNO", Some(10))
.unwrap()
.to_sys(),
ScmpAction::Errno(10).to_sys()
);
assert_eq!(
ScmpAction::from_str("SCMP_ACT_TRACE", Some(10))
.unwrap()
.to_sys(),
ScmpAction::Trace(10).to_sys()
);
assert_eq!(
ScmpArch::from_str("SCMP_ARCH_X86_64").unwrap().to_sys(),
ScmpArch::X8664.to_sys()
);
}
#[test]
fn test_check_version() {
assert!(check_version(ScmpVersion::from((2, 4, 0))).unwrap());
assert!(!check_version(ScmpVersion::from((100, 100, 100))).unwrap());
}
#[test]
fn test_check_api() {
assert!(check_api(3, ScmpVersion::from((2, 4, 0))).unwrap());
assert!(!check_api(100, ScmpVersion::from((2, 4, 0))).unwrap());
}
#[test]
fn test_ensure_supported_version() {
assert!(ensure_supported_version("test", ScmpVersion::from((2, 4, 0))).is_ok());
assert!(ensure_supported_version("test", ScmpVersion::from((100, 100, 100))).is_err());
}
#[test]
fn test_ensure_supported_api() {
assert!(ensure_supported_api("test", 3, ScmpVersion::from((2, 4, 0))).is_ok());
assert!(ensure_supported_api("test", 100, ScmpVersion::from((2, 4, 0))).is_err());
}
#[test]
#[allow(deprecated)]
fn test_get_library_version() {
let ret = ScmpVersion::current().unwrap();
assert_eq!(ret, get_library_version().unwrap());
println!(
"test_get_library_version: {}.{}.{}",
ret.major, ret.minor, ret.micro
);
}
#[test]
#[allow(deprecated)]
fn test_get_native_arch() {
let ret = ScmpArch::native().unwrap();
assert_eq!(ret, get_native_arch().unwrap());
println!("test_get_native_arch: native arch is {:?}", ret);
}
#[test]
fn test_get_api() {
let ret = get_api().unwrap();
println!("test_get_api: Got API level of {}", ret);
}
#[test]
fn test_set_api() {
let expected_api = 1;
set_api(expected_api).unwrap();
let api = get_api().unwrap();
assert_eq!(expected_api, api);
}
#[test]
fn test_scmpargcompare() {
assert_eq!(
ScmpArgCompare::new(0, ScmpCompareOp::NotEqual, 8),
ScmpArgCompare(scmp_arg_cmp {
arg: 0,
op: scmp_compare::SCMP_CMP_NE,
datum_a: 8,
datum_b: 0,
})
);
assert_eq!(
ScmpArgCompare::new(0, ScmpCompareOp::MaskedEqual(0b0010), 2),
ScmpArgCompare(scmp_arg_cmp {
arg: 0,
op: scmp_compare::SCMP_CMP_MASKED_EQ,
datum_a: 0b0010,
datum_b: 2,
})
);
assert_eq!(
scmp_arg_cmp::from(ScmpArgCompare::new(0, ScmpCompareOp::NotEqual, 8)),
scmp_arg_cmp {
arg: 0,
op: scmp_compare::SCMP_CMP_NE,
datum_a: 8,
datum_b: 0,
}
);
assert_eq!(
scmp_arg_cmp::from(&ScmpArgCompare::new(0, ScmpCompareOp::NotEqual, 8)),
scmp_arg_cmp {
arg: 0,
op: scmp_compare::SCMP_CMP_NE,
datum_a: 8,
datum_b: 0,
}
);
}
#[test]
fn test_set_syscall_priority() {
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::KillThread).unwrap();
let syscall = get_syscall_from_name("open", None).unwrap();
let priority = 100;
assert!(ctx.set_syscall_priority(syscall, priority).is_ok());
assert!(ctx.set_syscall_priority(-1, priority).is_err());
}
#[test]
fn test_filter_attributes() {
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::KillThread).unwrap();
ctx.set_no_new_privs_bit(false).unwrap();
let ret = ctx.get_no_new_privs_bit().unwrap();
assert!(!ret);
}
#[test]
fn test_filter_reset() {
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::KillThread).unwrap();
ctx.reset(ScmpAction::Allow).unwrap();
let action = ctx.get_filter_attr(ScmpFilterAttr::ActDefault).unwrap();
let expected_action: u32 = ScmpAction::Allow.to_sys();
assert_eq!(expected_action, action);
}
#[test]
fn test_get_syscall_name_from_arch() {
let name = get_syscall_name_from_arch(ScmpArch::Arm, 5).unwrap();
println!(
"test_get_syscall_from_name: Got syscall name of 5 on ARM arch as {}",
name
);
}
#[test]
fn test_get_syscall_from_name() {
let num = get_syscall_from_name("open", None).unwrap();
println!(
"test_get_syscall_from_name: Got syscall number of open on native arch as {}",
num
);
let num = get_syscall_from_name("open", Some(ScmpArch::Arm)).unwrap();
println!(
"test_get_syscall_from_name: Got syscall number of open on ARM arch as {}",
num
);
}
#[test]
fn test_arch_functions() {
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
ctx.add_arch(ScmpArch::X86).unwrap();
let ret = ctx.is_arch_present(ScmpArch::X86).unwrap();
assert!(ret);
ctx.remove_arch(ScmpArch::X86).unwrap();
let ret = ctx.is_arch_present(ScmpArch::X86).unwrap();
assert!(!ret);
}
#[test]
fn test_merge_filters() {
let mut ctx1 = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
let mut ctx2 = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
let native_arch = ScmpArch::native().unwrap();
let mut prospective_arch = ScmpArch::Aarch64;
if native_arch == ScmpArch::Aarch64 {
prospective_arch = ScmpArch::X8664;
}
ctx2.add_arch(prospective_arch).unwrap();
ctx2.remove_arch(native_arch).unwrap();
ctx1.merge(ctx2).unwrap();
let ret = ctx1.is_arch_present(prospective_arch).unwrap();
assert!(ret);
}
#[test]
fn test_export_functions() {
let ctx = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
assert!(ctx.export_bpf(&mut stdout()).is_ok());
assert!(ctx.export_bpf(&mut -1).is_err());
assert!(ctx.export_pfc(&mut stdout()).is_ok());
assert!(ctx.export_pfc(&mut -1).is_err());
}
#[test]
fn test_rule_add_load() {
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
ctx.add_arch(ScmpArch::Native).unwrap();
let syscall = get_syscall_from_name("dup3", None).unwrap();
ctx.add_rule(ScmpAction::Errno(10), syscall).unwrap();
ctx.load().unwrap();
syscall_assert!(unsafe { libc::dup3(0, 100, libc::O_CLOEXEC) }, -10);
}
#[test]
fn test_rule_add_array_load() {
let mut cmps: Vec<ScmpArgCompare> = Vec::new();
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
ctx.add_arch(ScmpArch::Native).unwrap();
let syscall = get_syscall_from_name("process_vm_readv", None).unwrap();
let cmp1 = ScmpArgCompare::new(0, ScmpCompareOp::Equal, 10);
let cmp2 = ScmpArgCompare::new(2, ScmpCompareOp::Equal, 20);
cmps.push(cmp1);
cmps.push(cmp2);
ctx.add_rule_conditional(ScmpAction::Errno(111), syscall, &cmps)
.unwrap();
ctx.load().unwrap();
syscall_assert!(
unsafe { libc::process_vm_readv(10, std::ptr::null(), 0, std::ptr::null(), 0, 0) },
0
);
syscall_assert!(
unsafe { libc::process_vm_readv(10, std::ptr::null(), 20, std::ptr::null(), 0, 0) },
-111
);
}
#[test]
fn test_rule_add_exact_load() {
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
ctx.add_arch(ScmpArch::Native).unwrap();
let syscall = get_syscall_from_name("dup3", None).unwrap();
ctx.add_rule_exact(ScmpAction::Errno(10), syscall).unwrap();
ctx.load().unwrap();
syscall_assert!(unsafe { libc::dup3(0, 100, libc::O_CLOEXEC) }, -10);
}
#[test]
fn test_rule_add_exact_array_load() {
let mut cmps: Vec<ScmpArgCompare> = Vec::new();
let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
ctx.add_arch(ScmpArch::Native).unwrap();
let syscall = get_syscall_from_name("process_vm_readv", None).unwrap();
let cmp1 = ScmpArgCompare::new(0, ScmpCompareOp::Equal, 10);
let cmp2 = ScmpArgCompare::new(2, ScmpCompareOp::Equal, 20);
cmps.push(cmp1);
cmps.push(cmp2);
ctx.add_rule_conditional_exact(ScmpAction::Errno(111), syscall, &cmps)
.unwrap();
ctx.load().unwrap();
syscall_assert!(
unsafe { libc::process_vm_readv(10, std::ptr::null(), 0, std::ptr::null(), 0, 0) },
0
);
syscall_assert!(
unsafe { libc::process_vm_readv(10, std::ptr::null(), 20, std::ptr::null(), 0, 0) },
-111
);
}
#[test]
fn test_as_ptr() {
let ctx = ScmpFilterContext::new_filter(ScmpAction::Allow).unwrap();
assert_eq!(ctx.as_ptr(), ctx.ctx.as_ptr());
}
}