1#![forbid(unsafe_code)]
10
11#[cfg(not(any(target_os = "linux", target_os = "android")))]
12std::compile_error!("letmeind server and letmein-seccomp do not support non-Linux platforms.");
13
14use anyhow::{self as ah, Context as _};
15use seccompiler::{apply_filter_all_threads, BpfProgram};
16use std::env::consts::ARCH;
17
18#[cfg(has_seccomp_support)]
19const NULL: u64 = 0;
20#[cfg(has_seccomp_support)]
21const PTR: u8 = 0xFF;
22
23#[cfg(has_seccomp_support)]
24macro_rules! sys {
25 ($ident:ident) => {{
26 #[allow(clippy::useless_conversion)]
27 let id: i64 = libc::$ident.into();
28 id
29 }};
30}
31
32#[cfg(has_seccomp_support)]
33fn seccomp_cond(idx: u8, value: u64, bit_width: u8) -> ah::Result<seccompiler::SeccompCondition> {
34 use seccompiler::{SeccompCmpArgLen, SeccompCmpOp, SeccompCondition};
35
36 let bit_width = match bit_width {
37 PTR => {
38 #[cfg(target_pointer_width = "32")]
39 let bit_width = 32;
40
41 #[cfg(target_pointer_width = "64")]
42 let bit_width = 64;
43
44 bit_width
45 }
46 bit_width => bit_width,
47 };
48
49 let arglen = match bit_width {
50 32 => {
51 assert_eq!(value & 0xFFFF_FFFF_0000_0000, 0);
52 SeccompCmpArgLen::Dword
53 }
54 64 => SeccompCmpArgLen::Qword,
55 bit_width => panic!("seccomp_cond: Invalid bit_width: {bit_width}"),
56 };
57
58 Ok(SeccompCondition::new(idx, arglen, SeccompCmpOp::Eq, value)?)
59}
60
61#[cfg(has_seccomp_support)]
62macro_rules! args {
63 ($([ $arg:literal ] ($bit_width:expr) == $value:expr),*) => {
64 SeccompRule::new(
65 vec![
66 $(
67 seccomp_cond($arg, ($value) as _, $bit_width)?,
68 )*
69 ]
70 )?
71 };
72}
73
74pub fn seccomp_supported() -> bool {
76 cfg!(any(has_seccomp_support))
77}
78
79#[derive(Clone, Copy, Debug)]
81pub enum Allow {
82 Mmap,
83 Mprotect,
84 GetUidGid,
85 ArchPrctl { op: Option<u32> },
86 Dup,
87 Pipe,
88 Listen,
89 UnixAccept,
90 UnixConnect,
91 TcpAccept,
92 TcpConnect,
93 Netlink,
94 SetSockOpt { level_optname: Option<(i32, i32)> },
95 Access,
96 Open,
97 Read,
98 Write,
99 Ioctl { op: Option<u32> },
100 Fcntl { op: Option<u32> },
101 Stat,
102 Recv,
103 Send,
104 Signal,
105 SigAction,
106 Futex,
107 SetTidAddress,
108 Rseq,
109 Clone,
110 Exec,
111 Wait,
112 GetRlimit,
113 Uname,
114 Pidfd,
115}
116
117#[derive(Clone, Copy, Debug)]
119pub enum Action {
120 Kill,
122 Log,
124}
125
126pub struct Filter(BpfProgram);
128
129impl Filter {
130 pub fn compile(allow: &[Allow], deny_action: Action) -> ah::Result<Self> {
131 Self::compile_for_arch(allow, deny_action, ARCH)
132 }
133
134 #[cfg(has_seccomp_support)]
135 pub fn compile_for_arch(allow: &[Allow], deny_action: Action, arch: &str) -> ah::Result<Self> {
136 assert!(!allow.is_empty());
137
138 use seccompiler::{SeccompAction, SeccompFilter, SeccompRule};
139 use std::collections::BTreeMap;
140
141 type RulesMap = BTreeMap<i64, Vec<SeccompRule>>;
142
143 fn add_sys(map: &mut RulesMap, sys: i64) {
144 let _rules = map.entry(sys).or_default();
145 }
146
147 fn add_sys_args_match(map: &mut RulesMap, sys: i64, rule: SeccompRule) {
148 let rules = map.entry(sys).or_default();
149 rules.push(rule);
150 }
151
152 let mut map: RulesMap = [].into();
153
154 add_sys(&mut map, sys!(SYS_brk));
155 add_sys(&mut map, sys!(SYS_close));
156 #[cfg(target_os = "linux")]
157 add_sys(&mut map, sys!(SYS_close_range));
158 add_sys(&mut map, sys!(SYS_exit));
159 add_sys(&mut map, sys!(SYS_exit_group));
160 add_sys(&mut map, sys!(SYS_getpid));
161 add_sys(&mut map, sys!(SYS_getrandom));
162 add_sys(&mut map, sys!(SYS_gettid));
163 add_sys(&mut map, sys!(SYS_madvise));
164 add_sys(&mut map, sys!(SYS_munmap));
165 add_sys(&mut map, sys!(SYS_sched_getaffinity));
166 add_sys(&mut map, sys!(SYS_sigaltstack));
167 add_sys(&mut map, sys!(SYS_nanosleep));
168 add_sys(&mut map, sys!(SYS_clock_gettime));
169 add_sys(&mut map, sys!(SYS_clock_getres));
170 add_sys(&mut map, sys!(SYS_clock_nanosleep));
171 add_sys(&mut map, sys!(SYS_gettimeofday));
172
173 fn add_read_write_rules(map: &mut RulesMap) {
174 add_sys(map, sys!(SYS_epoll_create1));
175 add_sys(map, sys!(SYS_epoll_ctl));
176 add_sys(map, sys!(SYS_epoll_pwait));
177 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
178 add_sys(map, sys!(SYS_epoll_pwait2));
179 #[cfg(target_arch = "x86_64")]
180 add_sys(map, sys!(SYS_epoll_wait));
181 add_sys(map, sys!(SYS_lseek));
182 #[cfg(target_arch = "x86_64")]
183 add_sys(map, sys!(SYS_poll));
184 add_sys(map, sys!(SYS_ppoll));
185 add_sys(map, sys!(SYS_pselect6));
186 }
187
188 for allow in allow {
189 match *allow {
190 Allow::Mmap => {
191 add_sys(&mut map, sys!(SYS_mmap));
192 add_sys(&mut map, sys!(SYS_mremap));
193 add_sys(&mut map, sys!(SYS_munmap));
194 }
195 Allow::Mprotect => {
196 add_sys(&mut map, sys!(SYS_mprotect));
197 }
198 Allow::GetUidGid => {
199 add_sys(&mut map, sys!(SYS_getuid));
200 add_sys(&mut map, sys!(SYS_geteuid));
201 add_sys(&mut map, sys!(SYS_getgid));
202 add_sys(&mut map, sys!(SYS_getegid));
203 }
204 Allow::ArchPrctl { op: _ } => {
205 #[cfg(target_arch = "x86_64")]
207 add_sys(&mut map, sys!(SYS_arch_prctl));
208 }
209 Allow::Dup => {
210 add_sys(&mut map, sys!(SYS_dup));
211 #[cfg(target_arch = "x86_64")]
212 add_sys(&mut map, sys!(SYS_dup2));
213 add_sys(&mut map, sys!(SYS_dup3));
214 }
215 Allow::Pipe => {
216 #[cfg(target_arch = "x86_64")]
217 add_sys(&mut map, sys!(SYS_pipe));
218 add_sys(&mut map, sys!(SYS_pipe2));
219 }
220 Allow::Listen => {
221 add_sys(&mut map, sys!(SYS_bind));
222 add_sys(&mut map, sys!(SYS_listen));
223 }
224 Allow::UnixAccept => {
225 add_sys(&mut map, sys!(SYS_accept4));
226 add_sys_args_match(&mut map, sys!(SYS_socket), args!([0](32) == libc::AF_UNIX));
227 add_sys(&mut map, sys!(SYS_getsockopt));
228 add_sys(&mut map, sys!(SYS_getpeername));
229 }
230 Allow::UnixConnect => {
231 add_sys(&mut map, sys!(SYS_connect));
232 add_sys_args_match(&mut map, sys!(SYS_socket), args!([0](32) == libc::AF_UNIX));
233 add_sys(&mut map, sys!(SYS_getsockopt));
234 add_sys(&mut map, sys!(SYS_getpeername));
235 }
236 Allow::TcpAccept => {
237 add_sys(&mut map, sys!(SYS_accept4));
238 add_sys_args_match(&mut map, sys!(SYS_socket), args!([0](32) == libc::AF_INET));
239 add_sys_args_match(
240 &mut map,
241 sys!(SYS_socket),
242 args!([0](32) == libc::AF_INET6),
243 );
244 add_sys(&mut map, sys!(SYS_getsockopt));
245 add_sys(&mut map, sys!(SYS_getpeername));
246 }
247 Allow::TcpConnect => {
248 add_sys(&mut map, sys!(SYS_connect));
249 add_sys_args_match(&mut map, sys!(SYS_socket), args!([0](32) == libc::AF_INET));
250 add_sys_args_match(
251 &mut map,
252 sys!(SYS_socket),
253 args!([0](32) == libc::AF_INET6),
254 );
255 add_sys(&mut map, sys!(SYS_getsockopt));
256 add_sys(&mut map, sys!(SYS_getpeername));
257 }
258 Allow::Netlink => {
259 add_sys(&mut map, sys!(SYS_connect));
260 add_sys_args_match(
261 &mut map,
262 sys!(SYS_socket),
263 args!([0](32) == libc::AF_NETLINK),
264 );
265 add_sys(&mut map, sys!(SYS_getsockopt));
266 }
267 Allow::SetSockOpt { level_optname } => {
268 if let Some((level, optname)) = level_optname {
269 add_sys_args_match(
270 &mut map,
271 sys!(SYS_setsockopt),
272 args!([1](32) == level, [2](32) == optname),
273 );
274 } else {
275 add_sys(&mut map, sys!(SYS_setsockopt));
276 }
277 }
278 Allow::Access => {
279 #[cfg(target_arch = "x86_64")]
280 add_sys(&mut map, sys!(SYS_access));
281 add_sys(&mut map, sys!(SYS_faccessat));
282 #[cfg(target_os = "linux")]
283 add_sys(&mut map, sys!(SYS_faccessat2));
284 }
285 Allow::Open => {
286 #[cfg(target_arch = "x86_64")]
288 add_sys(&mut map, sys!(SYS_open));
289 add_sys(&mut map, sys!(SYS_openat));
290 }
291 Allow::Read => {
292 add_sys(&mut map, sys!(SYS_pread64));
293 add_sys(&mut map, sys!(SYS_preadv2));
294 add_sys(&mut map, sys!(SYS_read));
295 add_sys(&mut map, sys!(SYS_readv));
296 add_read_write_rules(&mut map);
297 }
298 Allow::Write => {
299 add_sys(&mut map, sys!(SYS_fdatasync));
300 add_sys(&mut map, sys!(SYS_fsync));
301 add_sys(&mut map, sys!(SYS_pwrite64));
302 add_sys(&mut map, sys!(SYS_pwritev2));
303 add_sys(&mut map, sys!(SYS_write));
304 add_sys(&mut map, sys!(SYS_writev));
305 add_read_write_rules(&mut map);
306 }
307 Allow::Ioctl { op: _ } => {
308 add_sys(&mut map, sys!(SYS_ioctl));
310 }
311 Allow::Fcntl { op } => match op {
312 Some(op) => {
313 add_sys_args_match(&mut map, sys!(SYS_fcntl), args!([1](32) == op));
314 }
315 None => {
316 add_sys(&mut map, sys!(SYS_fcntl));
317 }
318 },
319 Allow::Stat => {
320 add_sys(&mut map, sys!(SYS_fstat));
321 add_sys(&mut map, sys!(SYS_statx));
322 add_sys(&mut map, sys!(SYS_newfstatat));
323 }
324 Allow::Recv => {
325 add_sys(&mut map, sys!(SYS_recvfrom));
326 add_sys(&mut map, sys!(SYS_recvmsg));
327 add_sys(&mut map, sys!(SYS_recvmmsg));
328 }
329 Allow::Send => {
330 add_sys(&mut map, sys!(SYS_sendto));
331 add_sys(&mut map, sys!(SYS_sendmsg));
332 add_sys(&mut map, sys!(SYS_sendmmsg));
333 }
334 Allow::Signal => {
335 add_sys(&mut map, sys!(SYS_rt_sigreturn));
336 add_sys(&mut map, sys!(SYS_rt_sigprocmask));
337 }
338 Allow::SigAction => {
339 add_sys(&mut map, sys!(SYS_rt_sigaction));
340 }
341 Allow::Futex => {
342 add_sys(&mut map, sys!(SYS_futex));
343 add_sys(&mut map, sys!(SYS_get_robust_list));
344 add_sys(&mut map, sys!(SYS_set_robust_list));
345 #[cfg(all(target_arch = "x86_64", target_os = "linux"))]
346 add_sys(&mut map, sys!(SYS_futex_waitv));
347 }
351 Allow::SetTidAddress => {
352 add_sys(&mut map, sys!(SYS_set_tid_address));
353 }
354 Allow::Rseq => {
355 #[cfg(target_os = "linux")]
356 add_sys(&mut map, sys!(SYS_rseq));
357 }
358 Allow::Clone => {
359 #[cfg(target_os = "linux")]
360 add_sys(&mut map, sys!(SYS_clone3));
361 #[cfg(target_arch = "aarch64")]
362 add_sys(&mut map, sys!(SYS_clone));
363 }
364 Allow::Exec => {
365 add_sys(&mut map, sys!(SYS_execve));
367 }
368 Allow::Wait => {
369 add_sys(&mut map, sys!(SYS_wait4));
370 }
371 Allow::GetRlimit => {
372 add_sys_args_match(
373 &mut map,
374 sys!(SYS_prlimit64),
375 args!([0](32) == 0, [2](PTR) == NULL),
376 );
377 }
378 Allow::Uname => {
379 add_sys(&mut map, sys!(SYS_uname));
380 }
381 Allow::Pidfd => {
382 add_sys(&mut map, sys!(SYS_pidfd_open));
383 }
384 }
385 }
386
387 let filter = SeccompFilter::new(
388 map,
389 match deny_action {
390 Action::Kill => SeccompAction::KillProcess,
391 Action::Log => SeccompAction::Log,
392 },
393 SeccompAction::Allow,
394 arch.try_into().context("Unsupported CPU ARCH")?,
395 )
396 .context("Create seccomp filter")?;
397
398 let filter: BpfProgram = filter.try_into().context("Seccomp to BPF")?;
399
400 Ok(Self(filter))
401 }
402
403 #[cfg(not(has_seccomp_support))]
404 pub fn compile_for_arch(
405 _allow: &[Allow],
406 _deny_action: Action,
407 _arch: &str,
408 ) -> ah::Result<Self> {
409 Err(ah::format_err!("seccomp is not supported on this platform"))
410 }
411
412 pub fn install(&self) -> ah::Result<()> {
413 apply_filter_all_threads(&self.0).context("Apply seccomp filter")
414 }
415}
416
417