pub mod common_syscall_allow_lists;
mod internal;
mod syscalls;
#[allow(
non_camel_case_types,
non_upper_case_globals,
non_snake_case,
dead_code,
unreachable_pub
)]
mod sys {
include!(concat!(env!("OUT_DIR"), "/security_sys.rs"));
}
use self::internal::RawRule;
use crate::BootstrapResult;
use anyhow::bail;
use sys::PR_GET_SECCOMP;
pub use self::syscalls::Syscall;
pub type RawOsErrorNum = u16;
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u32)]
pub enum ViolationAction {
KillProcess = sys::SCMP_ACT_KILL_PROCESS,
AllowAndLog = sys::SCMP_ACT_LOG,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ArgCmpValue(u64);
impl ArgCmpValue {
pub fn from_static<T>(val: &'static T) -> Self {
Self(val as *const T as u64)
}
}
impl<T: Into<u64>> From<T> for ArgCmpValue {
fn from(val: T) -> Self {
Self(val.into())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ArgCmp {
NotEqual {
arg_idx: u32,
value: ArgCmpValue,
},
LessThan {
arg_idx: u32,
value: ArgCmpValue,
},
LessThanOrEqual {
arg_idx: u32,
value: ArgCmpValue,
},
Equal {
arg_idx: u32,
value: ArgCmpValue,
},
GreaterThanOrEqual {
arg_idx: u32,
value: ArgCmpValue,
},
GreaterThan {
arg_idx: u32,
value: ArgCmpValue,
},
EqualMasked {
arg_idx: u32,
mask: u64,
value: ArgCmpValue,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum Rule {
Allow(Syscall, Vec<ArgCmp>),
AllowAndLog(Syscall, Vec<ArgCmp>),
ReturnError(Syscall, RawOsErrorNum, Vec<ArgCmp>),
}
pub fn enable_syscall_sandboxing(
violation_action: ViolationAction,
exception_rules: &Vec<Rule>,
) -> BootstrapResult<()> {
let ctx = unsafe { sys::seccomp_init(violation_action as u32) };
if ctx.is_null() {
bail!("failed to initialize seccomp context");
}
for rule in exception_rules {
let RawRule {
action,
syscall,
arg_cmps,
} = rule.into();
let init_res = unsafe {
sys::seccomp_rule_add_exact_array(
ctx,
action,
syscall,
arg_cmps.len().try_into().unwrap(),
arg_cmps.as_ptr(),
)
};
if init_res != 0 {
bail!(
"failed to add seccomp exception rule {:#?} with error code {}",
rule,
init_res
);
}
}
let load_res = unsafe { sys::seccomp_load(ctx) };
if load_res != 0 {
bail!("failed to load seccomp rules with error code {}", load_res);
}
Ok(())
}
#[cfg(target_arch = "x86_64")]
pub fn forbid_x86_64_cpu_cycle_counter() {
unsafe {
sys::prctl(
sys::PR_SET_TSC.try_into().unwrap(),
sys::PR_TSC_SIGSEGV,
0,
0,
0,
)
};
}
pub fn is_syscall_sandboxing_enabled_for_current_thread() -> BootstrapResult<bool> {
const SECCOMP_MODE_NONE: i32 = 0;
const SECCOMP_MODE_FILTER: i32 = 2;
let current_seccomp_mode = unsafe { sys::prctl(PR_GET_SECCOMP as i32) };
match current_seccomp_mode {
SECCOMP_MODE_NONE => Ok(false),
SECCOMP_MODE_FILTER => Ok(true),
_ => bail!("Unable to determine the current seccomp mode. Perhaps the kernel was not configured with CONFIG_SECCOMP?")
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __allow_list {
(
$(#[$attr:meta])*
$vis:vis static $SET_NAME:ident = $rules:tt
) => {
$crate::security::allow_list!( @doc
[],
$rules,
{
$(#[$attr])*
$vis static $SET_NAME = $rules
}
);
};
( @doc
[ $($docs:expr)* ],
[ $(#[$attr:meta])* ..$OTHER_SET:ident $(, $($rest:tt)+ )? ],
$allow_list_def:tt
) => {
$crate::security::allow_list!( @doc
[
$($docs)*
concat!("* all the syscalls from the [`", stringify!($OTHER_SET), "`] allow list")
],
[ $( $( $rest )+ )? ],
$allow_list_def
);
};
( @doc
[ $($docs:expr)* ],
[ $(#[$attr:meta])* $syscall:ident if $arg_cmp:tt $(, $($rest:tt)+ )? ],
$allow_list_def:tt
) => {
$crate::security::allow_list!( @doc
[
$($docs)*
concat!(
"* [",
stringify!($syscall),
"](https://man7.org/linux/man-pages/man2/",
stringify!($syscall),
".2.html) with argument conditions (refer to the allow list source code for more information)"
)
],
[ $( $( $rest )+ )? ],
$allow_list_def
);
};
( @doc
[ $($docs:expr)* ],
[ $(#[$attr:meta])* $syscall:ident $(, $($rest:tt)+ )? ],
$allow_list_def:tt
) => {
$crate::security::allow_list!( @doc
[
$($docs)*
concat!(
"* [",
stringify!($syscall),
"](https://man7.org/linux/man-pages/man2/",
stringify!($syscall),
".2.html)"
)
],
[ $( $( $rest )+ )? ],
$allow_list_def
);
};
( @doc
[ $($docs:expr)* ],
[],
{
$(#[$attr:meta])*
$vis:vis static $SET_NAME:ident = $rules:tt
}
) => {
$(#[$attr])*
$( #[doc = $docs] )*
$vis static $SET_NAME:
$crate::reexports_for_macros::once_cell::sync::Lazy<Vec<$crate::security::Rule>> =
$crate::reexports_for_macros::once_cell::sync::Lazy::new(|| {
let mut list = vec![];
#[allow(clippy::vec_init_then_push)]
{
$crate::security::allow_list!( @rule list, $rules );
}
list
});
};
( @rule
$list:ident,
[
$(#[$attr:meta])*
..$OTHER_SET:ident
$(, $($rest:tt)+ )?
]
) => {
$(#[$attr])*
$list.extend_from_slice(&$OTHER_SET);
$crate::security::allow_list!( @rule $list, [ $( $( $rest )+ )? ] );
};
( @rule
$list:ident,
[
$(#[$attr:meta])*
$syscall:ident if [ $( $arg_cmp:expr ),+ ]
$(, $($rest:tt)+ )?
]
) => {
$(#[$attr])*
$list.push($crate::security::Rule::Allow(
$crate::security::Syscall::$syscall,
vec![ $( $arg_cmp ),+ ]
));
$crate::security::allow_list!( @rule $list, [ $( $( $rest )+ )? ] );
};
( @rule
$list:ident,
[
$(#[$attr:meta])*
$syscall:ident
$(, $($rest:tt)+ )?
]
) => {
$(#[$attr])*
$list.push($crate::security::Rule::Allow(
$crate::security::Syscall::$syscall,
vec![]
));
$crate::security::allow_list!( @rule $list, [ $( $( $rest )+ )? ] );
};
( @rule $list:ident, [] ) => {}
}
#[doc(inline)]
pub use __allow_list as allow_list;