Skip to main content

sandlock_core/seccomp/
syscall.rs

1//! `Syscall` — checked syscall number newtype.
2//!
3//! Closes the footgun where `add_handler(-5, h)` would compile but
4//! silently never fire because the cBPF filter cannot emit a JEQ for
5//! an architecture-unknown syscall number.
6
7use thiserror::Error;
8
9#[derive(Debug, Error, PartialEq, Eq)]
10pub enum SyscallError {
11    #[error("syscall number {0} is negative")]
12    Negative(i64),
13    #[error("syscall number {0} is unknown for the current architecture")]
14    UnknownForArch(i64),
15}
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
18pub struct Syscall(i64);
19
20impl Syscall {
21    /// Validates that `nr` is non-negative and known on the current architecture.
22    pub fn checked(nr: i64) -> Result<Self, SyscallError> {
23        if nr < 0 {
24            return Err(SyscallError::Negative(nr));
25        }
26        if !crate::arch::is_known_syscall(nr) {
27            return Err(SyscallError::UnknownForArch(nr));
28        }
29        Ok(Self(nr))
30    }
31
32    pub fn raw(self) -> i64 {
33        self.0
34    }
35}
36
37impl TryFrom<i64> for Syscall {
38    type Error = SyscallError;
39    fn try_from(nr: i64) -> Result<Self, Self::Error> {
40        Self::checked(nr)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn checked_accepts_valid_openat() {
50        let s = Syscall::checked(libc::SYS_openat).expect("openat is valid");
51        assert_eq!(s.raw(), libc::SYS_openat);
52    }
53
54    #[test]
55    fn checked_rejects_negative() {
56        match Syscall::checked(-5) {
57            Err(SyscallError::Negative(-5)) => {}
58            other => panic!("expected Negative(-5), got {:?}", other),
59        }
60    }
61
62    #[test]
63    fn checked_rejects_arch_unknown() {
64        // 99_999 is above any reasonable MAX_SYSCALL_NR.
65        match Syscall::checked(99_999) {
66            Err(SyscallError::UnknownForArch(99_999)) => {}
67            other => panic!("expected UnknownForArch(99_999), got {:?}", other),
68        }
69    }
70
71    #[test]
72    fn try_from_i64_delegates_to_checked() {
73        let s: Syscall = libc::SYS_openat.try_into().expect("openat valid");
74        assert_eq!(s.raw(), libc::SYS_openat);
75        let bad: Result<Syscall, _> = (-1i64).try_into();
76        assert!(matches!(bad, Err(SyscallError::Negative(-1))));
77    }
78}