1use std::io::{Error, Result};
11
12#[cfg(not(target_os = "linux"))]
13compile_error!("seccomp filtering is only for Linux targets");
14
15#[link(name = "seccomp")]
16unsafe extern "C" {
17 static statime_kill_thread: u32;
18 static statime_kill_process: u32;
19 static statime_trap: u32;
20 static statime_err: u32;
21 static statime_log: u32;
22
23 fn statime_sandbox(def_action: u32) -> std::ffi::c_int;
24}
25
26pub enum Seccomp {
28 KillThread,
30 KillProcess,
32 Trap,
34 Fail,
36 Log,
38}
39
40pub use Seccomp::*;
41
42impl Seccomp {
43 fn into_u32(self) -> u32 {
44 unsafe {
47 match self {
48 Seccomp::KillThread => statime_kill_thread,
49 Seccomp::KillProcess => statime_kill_process,
50 Seccomp::Trap => statime_trap,
51 Seccomp::Fail => statime_err,
52 Seccomp::Log => statime_log,
53 }
54 }
55 }
56
57 pub fn enable(self) -> Result<()> {
68 let scmp_action = self.into_u32();
69
70 match unsafe { statime_sandbox(scmp_action) } {
72 0 => Ok(()),
73
74 x if x < 0 => Err(Error::from_raw_os_error(-x)),
75
76 _ => panic!("libseccomp returned a positive error"),
77 }
78 }
79}
80
81#[cfg(test)]
82mod test {
83 use super::*;
84
85 fn not_allowed() -> std::io::Result<()> {
86 std::env::set_current_dir(".").map(|_| ())
87 }
88
89 fn allowed() -> std::io::Result<()> {
90 std::net::UdpSocket::bind("0.0.0.0:0").map(|_| ())
91 }
92
93 #[test]
94 fn cannot_downgrade() {
95 Fail.enable().unwrap();
96 assert!(Log.enable().is_err());
97 }
98
99 #[test]
100 fn allowlisted_functions_are_fine() {
101 Fail.enable().unwrap();
102 assert!(allowed().is_ok());
103 }
104
105 #[test]
106 fn blocklisted_functions_are_stopped() {
107 Fail.enable().unwrap();
108 assert!(not_allowed().is_err());
109 }
110
111 #[test]
112 fn blocklisted_functions_are_fine_when_logged() {
113 Log.enable().unwrap();
114 assert!(not_allowed().is_ok());
115 }
116
117 #[test]
118 fn thread_is_stopped() {
119 let result = std::panic::catch_unwind(move || {
122 let handle = std::thread::spawn(|| {
123 KillThread.enable().unwrap();
124 let _ = not_allowed();
125 });
126 let _ = handle.join();
127 });
128
129 assert!(result.is_err());
130 }
131
132 #[allow(clippy::undocumented_unsafe_blocks)]
133 #[test]
134 fn process_is_stopped() {
135 for catchable in [true, false] {
136 unsafe {
137 match libc::fork() {
142 0 => {
143 unsafe extern "C" fn exit42(_: std::ffi::c_int) {
145 unsafe { libc::_exit(42) }
146 }
147 libc::signal(libc::SIGSYS, exit42 as *const () as libc::sighandler_t);
148
149 if catchable { Trap } else { KillProcess }.enable().unwrap();
150 let _ = not_allowed();
151
152 unreachable!();
153 }
154 child_pid => {
155 let mut status = 0;
157 assert_eq!(libc::wait(&mut status), child_pid);
158
159 if catchable {
160 assert!(libc::WIFEXITED(status));
161 assert_eq!(libc::WEXITSTATUS(status), 42);
162 } else {
163 assert!(libc::WIFSIGNALED(status));
164 assert_eq!(libc::WTERMSIG(status), libc::SIGSYS);
165 }
166 }
167 };
168 }
169 }
170 }
171}