use std::collections::BTreeSet;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Syscall {
Read,
Write,
Close,
Openat,
Fstat,
Lseek,
Mmap,
Munmap,
Brk,
RtSigaction,
RtSigprocmask,
Exit,
ExitGroup,
ClockGettime,
Getrandom,
}
impl Syscall {
pub fn number(self) -> u32 {
match self {
Syscall::Read => 0,
Syscall::Write => 1,
Syscall::Close => 3,
Syscall::Fstat => 5,
Syscall::Lseek => 8,
Syscall::Mmap => 9,
Syscall::Munmap => 11,
Syscall::Brk => 12,
Syscall::RtSigaction => 13,
Syscall::RtSigprocmask => 14,
Syscall::Openat => 257,
Syscall::ClockGettime => 228,
Syscall::ExitGroup => 231,
Syscall::Exit => 60,
Syscall::Getrandom => 318,
}
}
pub fn name(self) -> &'static str {
match self {
Syscall::Read => "read",
Syscall::Write => "write",
Syscall::Close => "close",
Syscall::Openat => "openat",
Syscall::Fstat => "fstat",
Syscall::Lseek => "lseek",
Syscall::Mmap => "mmap",
Syscall::Munmap => "munmap",
Syscall::Brk => "brk",
Syscall::RtSigaction => "rt_sigaction",
Syscall::RtSigprocmask => "rt_sigprocmask",
Syscall::Exit => "exit",
Syscall::ExitGroup => "exit_group",
Syscall::ClockGettime => "clock_gettime",
Syscall::Getrandom => "getrandom",
}
}
}
impl fmt::Display for Syscall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl FromStr for Syscall {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"read" => Ok(Syscall::Read),
"write" => Ok(Syscall::Write),
"close" => Ok(Syscall::Close),
"openat" => Ok(Syscall::Openat),
"fstat" => Ok(Syscall::Fstat),
"lseek" => Ok(Syscall::Lseek),
"mmap" => Ok(Syscall::Mmap),
"munmap" => Ok(Syscall::Munmap),
"brk" => Ok(Syscall::Brk),
"rt_sigaction" => Ok(Syscall::RtSigaction),
"rt_sigprocmask" => Ok(Syscall::RtSigprocmask),
"exit" => Ok(Syscall::Exit),
"exit_group" => Ok(Syscall::ExitGroup),
"clock_gettime" => Ok(Syscall::ClockGettime),
"getrandom" => Ok(Syscall::Getrandom),
_ => Err(format!("unknown syscall: '{s}'")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SyscallAllowlist {
pub syscalls: BTreeSet<Syscall>,
}
impl SyscallAllowlist {
pub fn from_names<I, S>(names: I) -> Result<Self, String>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut syscalls = BTreeSet::new();
for name in names {
syscalls.insert(Syscall::from_str(name.as_ref())?);
}
Ok(Self { syscalls })
}
pub fn permits(&self, syscall: Syscall) -> bool {
self.syscalls.contains(&syscall)
}
pub fn iter(&self) -> impl Iterator<Item = Syscall> + '_ {
self.syscalls.iter().copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_names_parse_and_round_trip() {
for name in [
"read",
"write",
"close",
"openat",
"fstat",
"lseek",
"mmap",
"munmap",
"brk",
"rt_sigaction",
"rt_sigprocmask",
"exit",
"exit_group",
"clock_gettime",
"getrandom",
] {
let sc = Syscall::from_str(name).unwrap();
assert_eq!(sc.name(), name);
assert_eq!(sc.to_string(), name);
}
}
#[test]
fn unknown_name_is_rejected() {
assert!(Syscall::from_str("ptrace").is_err());
assert!(SyscallAllowlist::from_names(["read", "execve"]).is_err());
}
#[test]
fn from_names_dedups_and_validates() {
let allow = SyscallAllowlist::from_names(["read", "write", "read", "close"]).unwrap();
assert_eq!(allow.syscalls.len(), 3);
assert!(allow.permits(Syscall::Read));
assert!(allow.permits(Syscall::Write));
assert!(allow.permits(Syscall::Close));
assert!(!allow.permits(Syscall::Openat));
}
#[test]
fn iter_is_order_stable() {
let allow = SyscallAllowlist::from_names(["write", "read", "close"]).unwrap();
let order: Vec<Syscall> = allow.iter().collect();
assert_eq!(order, vec![Syscall::Read, Syscall::Write, Syscall::Close]);
}
#[test]
fn known_syscall_numbers_are_x86_64() {
assert_eq!(Syscall::Read.number(), 0);
assert_eq!(Syscall::Write.number(), 1);
assert_eq!(Syscall::Close.number(), 3);
assert_eq!(Syscall::Exit.number(), 60);
assert_eq!(Syscall::Openat.number(), 257);
assert_eq!(Syscall::ExitGroup.number(), 231);
}
}