1use std::collections::BTreeSet;
17use std::fmt;
18use std::str::FromStr;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub enum Syscall {
29 Read,
31 Write,
33 Close,
35 Openat,
37 Fstat,
39 Lseek,
41 Mmap,
43 Munmap,
45 Brk,
47 RtSigaction,
49 RtSigprocmask,
51 Exit,
53 ExitGroup,
55 ClockGettime,
57 Getrandom,
59}
60
61impl Syscall {
62 pub fn number(self) -> u32 {
67 match self {
68 Syscall::Read => 0,
69 Syscall::Write => 1,
70 Syscall::Close => 3,
71 Syscall::Fstat => 5,
72 Syscall::Lseek => 8,
73 Syscall::Mmap => 9,
74 Syscall::Munmap => 11,
75 Syscall::Brk => 12,
76 Syscall::RtSigaction => 13,
77 Syscall::RtSigprocmask => 14,
78 Syscall::Openat => 257,
79 Syscall::ClockGettime => 228,
80 Syscall::ExitGroup => 231,
81 Syscall::Exit => 60,
82 Syscall::Getrandom => 318,
83 }
84 }
85
86 pub fn name(self) -> &'static str {
88 match self {
89 Syscall::Read => "read",
90 Syscall::Write => "write",
91 Syscall::Close => "close",
92 Syscall::Openat => "openat",
93 Syscall::Fstat => "fstat",
94 Syscall::Lseek => "lseek",
95 Syscall::Mmap => "mmap",
96 Syscall::Munmap => "munmap",
97 Syscall::Brk => "brk",
98 Syscall::RtSigaction => "rt_sigaction",
99 Syscall::RtSigprocmask => "rt_sigprocmask",
100 Syscall::Exit => "exit",
101 Syscall::ExitGroup => "exit_group",
102 Syscall::ClockGettime => "clock_gettime",
103 Syscall::Getrandom => "getrandom",
104 }
105 }
106}
107
108impl fmt::Display for Syscall {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.write_str(self.name())
111 }
112}
113
114impl FromStr for Syscall {
115 type Err = String;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 match s {
119 "read" => Ok(Syscall::Read),
120 "write" => Ok(Syscall::Write),
121 "close" => Ok(Syscall::Close),
122 "openat" => Ok(Syscall::Openat),
123 "fstat" => Ok(Syscall::Fstat),
124 "lseek" => Ok(Syscall::Lseek),
125 "mmap" => Ok(Syscall::Mmap),
126 "munmap" => Ok(Syscall::Munmap),
127 "brk" => Ok(Syscall::Brk),
128 "rt_sigaction" => Ok(Syscall::RtSigaction),
129 "rt_sigprocmask" => Ok(Syscall::RtSigprocmask),
130 "exit" => Ok(Syscall::Exit),
131 "exit_group" => Ok(Syscall::ExitGroup),
132 "clock_gettime" => Ok(Syscall::ClockGettime),
133 "getrandom" => Ok(Syscall::Getrandom),
134 _ => Err(format!("unknown syscall: '{s}'")),
135 }
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Default)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147pub struct SyscallAllowlist {
148 pub syscalls: BTreeSet<Syscall>,
150}
151
152impl SyscallAllowlist {
153 pub fn from_names<I, S>(names: I) -> Result<Self, String>
157 where
158 I: IntoIterator<Item = S>,
159 S: AsRef<str>,
160 {
161 let mut syscalls = BTreeSet::new();
162 for name in names {
163 syscalls.insert(Syscall::from_str(name.as_ref())?);
164 }
165 Ok(Self { syscalls })
166 }
167
168 pub fn permits(&self, syscall: Syscall) -> bool {
170 self.syscalls.contains(&syscall)
171 }
172
173 pub fn iter(&self) -> impl Iterator<Item = Syscall> + '_ {
175 self.syscalls.iter().copied()
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn known_names_parse_and_round_trip() {
185 for name in [
186 "read",
187 "write",
188 "close",
189 "openat",
190 "fstat",
191 "lseek",
192 "mmap",
193 "munmap",
194 "brk",
195 "rt_sigaction",
196 "rt_sigprocmask",
197 "exit",
198 "exit_group",
199 "clock_gettime",
200 "getrandom",
201 ] {
202 let sc = Syscall::from_str(name).unwrap();
203 assert_eq!(sc.name(), name);
204 assert_eq!(sc.to_string(), name);
205 }
206 }
207
208 #[test]
209 fn unknown_name_is_rejected() {
210 assert!(Syscall::from_str("ptrace").is_err());
211 assert!(SyscallAllowlist::from_names(["read", "execve"]).is_err());
212 }
213
214 #[test]
215 fn from_names_dedups_and_validates() {
216 let allow = SyscallAllowlist::from_names(["read", "write", "read", "close"]).unwrap();
217 assert_eq!(allow.syscalls.len(), 3);
218 assert!(allow.permits(Syscall::Read));
219 assert!(allow.permits(Syscall::Write));
220 assert!(allow.permits(Syscall::Close));
221 assert!(!allow.permits(Syscall::Openat));
222 }
223
224 #[test]
225 fn iter_is_order_stable() {
226 let allow = SyscallAllowlist::from_names(["write", "read", "close"]).unwrap();
227 let order: Vec<Syscall> = allow.iter().collect();
229 assert_eq!(order, vec![Syscall::Read, Syscall::Write, Syscall::Close]);
230 }
231
232 #[test]
233 fn known_syscall_numbers_are_x86_64() {
234 assert_eq!(Syscall::Read.number(), 0);
235 assert_eq!(Syscall::Write.number(), 1);
236 assert_eq!(Syscall::Close.number(), 3);
237 assert_eq!(Syscall::Exit.number(), 60);
238 assert_eq!(Syscall::Openat.number(), 257);
239 assert_eq!(Syscall::ExitGroup.number(), 231);
240 }
241}