sandlock_core/seccomp/
syscall.rs1use thiserror::Error;
9
10pub fn syscall_name_to_nr(name: &str) -> Option<u32> {
21 name.parse::<syscalls::Sysno>()
22 .ok()
23 .or_else(|| libc_name_alias(name).and_then(|aka| aka.parse::<syscalls::Sysno>().ok()))
24 .map(|s| s.id() as u32)
25}
26
27fn libc_name_alias(name: &str) -> Option<&'static str> {
35 match name {
36 "newfstatat" => Some("fstatat"),
37 _ => None,
38 }
39}
40
41#[derive(Debug, Error, PartialEq, Eq)]
42pub enum SyscallError {
43 #[error("syscall number {0} is negative")]
44 Negative(i64),
45 #[error("syscall number {0} is unknown for the current architecture")]
46 UnknownForArch(i64),
47}
48
49#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
50pub struct Syscall(i64);
51
52impl Syscall {
53 pub fn checked(nr: i64) -> Result<Self, SyscallError> {
55 if nr < 0 {
56 return Err(SyscallError::Negative(nr));
57 }
58 if !crate::arch::is_known_syscall(nr) {
59 return Err(SyscallError::UnknownForArch(nr));
60 }
61 Ok(Self(nr))
62 }
63
64 pub fn raw(self) -> i64 {
65 self.0
66 }
67}
68
69impl TryFrom<i64> for Syscall {
70 type Error = SyscallError;
71 fn try_from(nr: i64) -> Result<Self, Self::Error> {
72 Self::checked(nr)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn checked_accepts_valid_openat() {
82 let s = Syscall::checked(libc::SYS_openat).expect("openat is valid");
83 assert_eq!(s.raw(), libc::SYS_openat);
84 }
85
86 #[test]
87 fn checked_rejects_negative() {
88 match Syscall::checked(-5) {
89 Err(SyscallError::Negative(-5)) => {}
90 other => panic!("expected Negative(-5), got {:?}", other),
91 }
92 }
93
94 #[test]
95 fn checked_rejects_arch_unknown() {
96 match Syscall::checked(99_999) {
98 Err(SyscallError::UnknownForArch(99_999)) => {}
99 other => panic!("expected UnknownForArch(99_999), got {:?}", other),
100 }
101 }
102
103 #[test]
104 fn try_from_i64_delegates_to_checked() {
105 let s: Syscall = libc::SYS_openat.try_into().expect("openat valid");
106 assert_eq!(s.raw(), libc::SYS_openat);
107 let bad: Result<Syscall, _> = (-1i64).try_into();
108 assert!(matches!(bad, Err(SyscallError::Negative(-1))));
109 }
110
111 #[test]
117 fn name_to_nr_matches_libc_for_stable_names() {
118 let cases: &[(&str, i64)] = &[
119 ("mount", libc::SYS_mount),
120 ("openat", libc::SYS_openat),
121 ("connect", libc::SYS_connect),
122 ("clone", libc::SYS_clone),
123 ("clone3", libc::SYS_clone3),
124 ("execve", libc::SYS_execve),
125 ("ioctl", libc::SYS_ioctl),
126 ("ptrace", libc::SYS_ptrace),
127 ("userfaultfd", libc::SYS_userfaultfd),
128 ("bpf", libc::SYS_bpf),
129 ("statx", libc::SYS_statx),
130 ("getrandom", libc::SYS_getrandom),
131 ("io_uring_setup", libc::SYS_io_uring_setup),
132 ];
133 for &(name, expected) in cases {
134 assert_eq!(
135 syscall_name_to_nr(name),
136 Some(expected as u32),
137 "{name} should resolve to libc::SYS_{name} = {expected}"
138 );
139 }
140 }
141
142 #[test]
143 fn name_to_nr_rejects_non_syscall_names() {
144 assert_eq!(syscall_name_to_nr("definitely_not_a_syscall"), None);
145 assert_eq!(syscall_name_to_nr(""), None);
146 }
147
148 #[test]
152 fn name_to_nr_resolves_newfstatat_alias() {
153 assert_eq!(
154 syscall_name_to_nr("newfstatat"),
155 Some(libc::SYS_newfstatat as u32)
156 );
157 }
158}