/rust/registry/src/index.crates.io-1949cf8c6b5b557f/seccompiler-0.5.0/src/lib.rs
Line | Count | Source |
1 | | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
2 | | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause |
3 | | #![deny(missing_docs)] |
4 | | #![cfg(target_endian = "little")] |
5 | | //! Provides easy-to-use Linux seccomp-bpf jailing. |
6 | | //! |
7 | | //! Seccomp is a Linux kernel security feature which enables a tight control over what kernel-level |
8 | | //! mechanisms a process has access to. This is typically used to reduce the attack surface and |
9 | | //! exposed resources when running untrusted code. This works by allowing users to write and set a |
10 | | //! BPF (Berkeley Packet Filter) program for each process or thread, that intercepts syscalls and |
11 | | //! decides whether the syscall is safe to execute. |
12 | | //! |
13 | | //! Writing BPF programs by hand is difficult and error-prone. This crate provides high-level |
14 | | //! wrappers for working with system call filtering. |
15 | | //! |
16 | | //! The core concept of the library is the filter. It is an abstraction that |
17 | | //! models a collection of syscall-mapped rules, coupled with on-match and |
18 | | //! default actions, that logically describes a policy for dispatching actions |
19 | | //! (e.g. Allow, Trap, Errno) for incoming system calls. |
20 | | //! |
21 | | //! Seccompiler provides constructs for defining filters, compiling them into |
22 | | //! loadable BPF programs and installing them in the kernel. |
23 | | //! |
24 | | //! Filters are defined either with a JSON file or using Rust code, with |
25 | | //! library-defined structures. Both representations are semantically equivalent |
26 | | //! and model the rules of the filter. Choosing one or the other depends on the use |
27 | | //! case and preference. |
28 | | //! |
29 | | //! # Supported platforms |
30 | | //! |
31 | | //! Due to the fact that seccomp is a Linux-specific feature, this crate is |
32 | | //! supported only on Linux systems. |
33 | | //! |
34 | | //! Supported host architectures: |
35 | | //! - Little-endian x86_64 |
36 | | //! - Little-endian aarch64 |
37 | | //! - Little-endian riscv64 |
38 | | //! |
39 | | //! # Terminology |
40 | | //! |
41 | | //! The smallest unit of the [`SeccompFilter`] is the [`SeccompCondition`], which is a |
42 | | //! comparison operation applied to the current system call. It’s parametrised by |
43 | | //! the argument index, the length of the argument, the operator and the actual |
44 | | //! expected value. |
45 | | //! |
46 | | //! Going one step further, a [`SeccompRule`] is a vector of [`SeccompCondition`]s, |
47 | | //! that must all match for the rule to be considered matched. In other words, a |
48 | | //! rule is a collection of **and-bound** conditions for a system call. |
49 | | //! |
50 | | //! Finally, at the top level, there’s the [`SeccompFilter`]. The filter can be |
51 | | //! viewed as a collection of syscall-associated rules, with a predefined on-match |
52 | | //! [`SeccompAction`] and a default [`SeccompAction`] that is returned if none of the rules match. |
53 | | //! |
54 | | //! In a filter, each system call number maps to a vector of **or-bound** rules. |
55 | | //! In order for the filter to match, it is enough that one rule associated to the |
56 | | //! system call matches. A system call may also map to an empty rule vector, which |
57 | | //! means that the system call will match, regardless of the actual arguments. |
58 | | //! |
59 | | //! # Examples |
60 | | //! |
61 | | //! The following example defines and installs a simple Rust filter, that sends SIGSYS for |
62 | | //! `accept4`, `fcntl(any, F_SETFD, FD_CLOEXEC, ..)` and `fcntl(any, F_GETFD, ...)`. |
63 | | //! It allows any other syscalls. |
64 | | //! |
65 | | //! ``` |
66 | | //! use seccompiler::{ |
67 | | //! BpfProgram, SeccompAction, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, SeccompFilter, |
68 | | //! SeccompRule, |
69 | | //! }; |
70 | | //! use std::convert::TryInto; |
71 | | //! |
72 | | //! let filter: BpfProgram = SeccompFilter::new( |
73 | | //! vec![ |
74 | | //! (libc::SYS_accept4, vec![]), |
75 | | //! ( |
76 | | //! libc::SYS_fcntl, |
77 | | //! vec![ |
78 | | //! SeccompRule::new(vec![ |
79 | | //! SeccompCondition::new( |
80 | | //! 1, |
81 | | //! SeccompCmpArgLen::Dword, |
82 | | //! SeccompCmpOp::Eq, |
83 | | //! libc::F_SETFD as u64, |
84 | | //! ) |
85 | | //! .unwrap(), |
86 | | //! SeccompCondition::new( |
87 | | //! 2, |
88 | | //! SeccompCmpArgLen::Dword, |
89 | | //! SeccompCmpOp::Eq, |
90 | | //! libc::FD_CLOEXEC as u64, |
91 | | //! ) |
92 | | //! .unwrap(), |
93 | | //! ]) |
94 | | //! .unwrap(), |
95 | | //! SeccompRule::new(vec![SeccompCondition::new( |
96 | | //! 1, |
97 | | //! SeccompCmpArgLen::Dword, |
98 | | //! SeccompCmpOp::Eq, |
99 | | //! libc::F_GETFD as u64, |
100 | | //! ) |
101 | | //! .unwrap()]) |
102 | | //! .unwrap(), |
103 | | //! ], |
104 | | //! ), |
105 | | //! ] |
106 | | //! .into_iter() |
107 | | //! .collect(), |
108 | | //! SeccompAction::Allow, |
109 | | //! SeccompAction::Trap, |
110 | | //! std::env::consts::ARCH.try_into().unwrap(), |
111 | | //! ) |
112 | | //! .unwrap() |
113 | | //! .try_into() |
114 | | //! .unwrap(); |
115 | | //! |
116 | | //! seccompiler::apply_filter(&filter).unwrap(); |
117 | | //! ``` |
118 | | //! |
119 | | //! |
120 | | //! This second example defines and installs an equivalent JSON filter (uses the `json` feature): |
121 | | //! |
122 | | //! ``` |
123 | | //! # #[cfg(feature = "json")] |
124 | | //! # { |
125 | | //! use seccompiler::BpfMap; |
126 | | //! use std::convert::TryInto; |
127 | | //! |
128 | | //! let json_input = r#"{ |
129 | | //! "main_thread": { |
130 | | //! "mismatch_action": "allow", |
131 | | //! "match_action": "trap", |
132 | | //! "filter": [ |
133 | | //! { |
134 | | //! "syscall": "accept4" |
135 | | //! }, |
136 | | //! { |
137 | | //! "syscall": "fcntl", |
138 | | //! "args": [ |
139 | | //! { |
140 | | //! "index": 1, |
141 | | //! "type": "dword", |
142 | | //! "op": "eq", |
143 | | //! "val": 2, |
144 | | //! "comment": "F_SETFD" |
145 | | //! }, |
146 | | //! { |
147 | | //! "index": 2, |
148 | | //! "type": "dword", |
149 | | //! "op": "eq", |
150 | | //! "val": 1, |
151 | | //! "comment": "FD_CLOEXEC" |
152 | | //! } |
153 | | //! ] |
154 | | //! }, |
155 | | //! { |
156 | | //! "syscall": "fcntl", |
157 | | //! "args": [ |
158 | | //! { |
159 | | //! "index": 1, |
160 | | //! "type": "dword", |
161 | | //! "op": "eq", |
162 | | //! "val": 1, |
163 | | //! "comment": "F_GETFD" |
164 | | //! } |
165 | | //! ] |
166 | | //! } |
167 | | //! ] |
168 | | //! } |
169 | | //! }"#; |
170 | | //! |
171 | | //! let filter_map: BpfMap = seccompiler::compile_from_json( |
172 | | //! json_input.as_bytes(), |
173 | | //! std::env::consts::ARCH.try_into().unwrap(), |
174 | | //! ) |
175 | | //! .unwrap(); |
176 | | //! let filter = filter_map.get("main_thread").unwrap(); |
177 | | //! |
178 | | //! seccompiler::apply_filter(&filter).unwrap(); |
179 | | //! |
180 | | //! # } |
181 | | //! ``` |
182 | | //! |
183 | | //! [`SeccompFilter`]: struct.SeccompFilter.html |
184 | | //! [`SeccompCondition`]: struct.SeccompCondition.html |
185 | | //! [`SeccompRule`]: struct.SeccompRule.html |
186 | | //! [`SeccompAction`]: enum.SeccompAction.html |
187 | | |
188 | | mod backend; |
189 | | #[cfg(feature = "json")] |
190 | | mod frontend; |
191 | | #[cfg(feature = "json")] |
192 | | mod syscall_table; |
193 | | |
194 | | #[cfg(feature = "json")] |
195 | | use std::convert::TryInto; |
196 | | #[cfg(feature = "json")] |
197 | | use std::io::Read; |
198 | | |
199 | | use std::collections::HashMap; |
200 | | use std::fmt::{Display, Formatter}; |
201 | | use std::io; |
202 | | |
203 | | #[cfg(feature = "json")] |
204 | | use frontend::json::{Error as JsonFrontendError, JsonCompiler}; |
205 | | |
206 | | // Re-export the IR public types. |
207 | | pub use backend::{ |
208 | | sock_filter, BpfProgram, BpfProgramRef, Error as BackendError, SeccompAction, SeccompCmpArgLen, |
209 | | SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule, TargetArch, |
210 | | }; |
211 | | |
212 | | // BPF structure definition for filter array. |
213 | | // See /usr/include/linux/filter.h . |
214 | | #[repr(C)] |
215 | | struct sock_fprog { |
216 | | pub len: ::std::os::raw::c_ushort, |
217 | | pub filter: *const sock_filter, |
218 | | } |
219 | | |
220 | | /// Library Result type. |
221 | | pub type Result<T> = std::result::Result<T, Error>; |
222 | | |
223 | | ///`BpfMap` is another type exposed by the library, which maps thread categories to BPF programs. |
224 | | pub type BpfMap = HashMap<String, BpfProgram>; |
225 | | |
226 | | /// Library errors. |
227 | | #[derive(Debug)] |
228 | | pub enum Error { |
229 | | /// Error originating in the backend compiler. |
230 | | Backend(BackendError), |
231 | | /// Attempting to install an empty filter. |
232 | | EmptyFilter, |
233 | | /// System error related to calling `prctl`. |
234 | | Prctl(io::Error), |
235 | | /// System error related to calling `seccomp` syscall. |
236 | | Seccomp(io::Error), |
237 | | /// Returned when calling `seccomp` with the thread sync flag (TSYNC) fails. Contains the pid |
238 | | /// of the thread that caused the failure. |
239 | | ThreadSync(libc::c_long), |
240 | | /// Json Frontend Error. |
241 | | #[cfg(feature = "json")] |
242 | | JsonFrontend(JsonFrontendError), |
243 | | } |
244 | | |
245 | | impl std::error::Error for Error { |
246 | 0 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
247 | | use self::Error::*; |
248 | | |
249 | 0 | match self { |
250 | 0 | Backend(error) => Some(error), |
251 | 0 | Prctl(error) => Some(error), |
252 | 0 | Seccomp(error) => Some(error), |
253 | 0 | ThreadSync(_) => None, |
254 | | #[cfg(feature = "json")] |
255 | | JsonFrontend(error) => Some(error), |
256 | 0 | _ => None, |
257 | | } |
258 | 0 | } |
259 | | } |
260 | | |
261 | | impl Display for Error { |
262 | 0 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { |
263 | | use self::Error::*; |
264 | | |
265 | 0 | match self { |
266 | 0 | Backend(error) => { |
267 | 0 | write!(f, "Backend error: {}", error) |
268 | | } |
269 | | EmptyFilter => { |
270 | 0 | write!(f, "Cannot install empty filter.") |
271 | | } |
272 | 0 | Prctl(errno) => { |
273 | 0 | write!(f, "Error calling `prctl`: {}", errno) |
274 | | } |
275 | 0 | Seccomp(errno) => { |
276 | 0 | write!(f, "Error calling `seccomp`: {}", errno) |
277 | | } |
278 | 0 | ThreadSync(pid) => { |
279 | 0 | write!( |
280 | 0 | f, |
281 | | "Seccomp filter synchronization failed in thread `{}`", |
282 | | pid |
283 | | ) |
284 | | } |
285 | | #[cfg(feature = "json")] |
286 | | JsonFrontend(error) => { |
287 | | write!(f, "Json Frontend error: {}", error) |
288 | | } |
289 | | } |
290 | 0 | } |
291 | | } |
292 | | |
293 | | impl From<BackendError> for Error { |
294 | 0 | fn from(value: BackendError) -> Self { |
295 | 0 | Self::Backend(value) |
296 | 0 | } |
297 | | } |
298 | | #[cfg(feature = "json")] |
299 | | impl From<JsonFrontendError> for Error { |
300 | | fn from(value: JsonFrontendError) -> Self { |
301 | | Self::JsonFrontend(value) |
302 | | } |
303 | | } |
304 | | |
305 | | /// Apply a BPF filter to the calling thread. |
306 | | /// |
307 | | /// # Arguments |
308 | | /// |
309 | | /// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed. |
310 | | /// |
311 | | /// [`BpfProgram`]: type.BpfProgram.html |
312 | 0 | pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> { |
313 | 0 | apply_filter_with_flags(bpf_filter, 0) |
314 | 0 | } |
315 | | |
316 | | /// Apply a BPF filter to the all threads in the process via the TSYNC feature. Please read the |
317 | | /// man page for seccomp (`man 2 seccomp`) for more information. |
318 | | /// |
319 | | /// # Arguments |
320 | | /// |
321 | | /// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed. |
322 | | /// |
323 | | /// [`BpfProgram`]: type.BpfProgram.html |
324 | 0 | pub fn apply_filter_all_threads(bpf_filter: BpfProgramRef) -> Result<()> { |
325 | 0 | apply_filter_with_flags(bpf_filter, libc::SECCOMP_FILTER_FLAG_TSYNC) |
326 | 0 | } |
327 | | |
328 | | /// Apply a BPF filter to the calling thread. |
329 | | /// |
330 | | /// # Arguments |
331 | | /// |
332 | | /// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed. |
333 | | /// * `flags` - A u64 representing a bitset of seccomp's flags parameter. |
334 | | /// |
335 | | /// [`BpfProgram`]: type.BpfProgram.html |
336 | 0 | fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: libc::c_ulong) -> Result<()> { |
337 | | // If the program is empty, don't install the filter. |
338 | 0 | if bpf_filter.is_empty() { |
339 | 0 | return Err(Error::EmptyFilter); |
340 | 0 | } |
341 | | |
342 | | // SAFETY: |
343 | | // Safe because syscall arguments are valid. |
344 | 0 | let rc = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) }; |
345 | 0 | if rc != 0 { |
346 | 0 | return Err(Error::Prctl(io::Error::last_os_error())); |
347 | 0 | } |
348 | | |
349 | 0 | let bpf_prog = sock_fprog { |
350 | 0 | len: bpf_filter.len() as u16, |
351 | 0 | filter: bpf_filter.as_ptr(), |
352 | 0 | }; |
353 | 0 | let bpf_prog_ptr = &bpf_prog as *const sock_fprog; |
354 | | |
355 | | // SAFETY: |
356 | | // Safe because the kernel performs a `copy_from_user` on the filter and leaves the memory |
357 | | // untouched. We can therefore use a reference to the BpfProgram, without needing ownership. |
358 | 0 | let rc = unsafe { |
359 | 0 | libc::syscall( |
360 | | libc::SYS_seccomp, |
361 | | libc::SECCOMP_SET_MODE_FILTER, |
362 | 0 | flags, |
363 | 0 | bpf_prog_ptr, |
364 | | ) |
365 | | }; |
366 | | |
367 | | #[allow(clippy::comparison_chain)] |
368 | | // Per manpage, if TSYNC fails, retcode is >0 and equals the pid of the thread that caused the |
369 | | // failure. Otherwise, error code is -1 and errno is set. |
370 | 0 | if rc < 0 { |
371 | 0 | return Err(Error::Seccomp(io::Error::last_os_error())); |
372 | 0 | } else if rc > 0 { |
373 | 0 | return Err(Error::ThreadSync(rc)); |
374 | 0 | } |
375 | | |
376 | 0 | Ok(()) |
377 | 0 | } |
378 | | |
379 | | /// Compile [`BpfProgram`]s from JSON. |
380 | | /// |
381 | | /// # Arguments |
382 | | /// |
383 | | /// * `reader` - [`std::io::Read`] object containing the JSON data conforming to the |
384 | | /// [JSON file format](https://github.com/rust-vmm/seccompiler/blob/master/docs/json_format.md). |
385 | | /// * `arch` - target architecture of the filter. |
386 | | /// |
387 | | /// [`BpfProgram`]: type.BpfProgram.html |
388 | | #[cfg(feature = "json")] |
389 | | pub fn compile_from_json<R: Read>(reader: R, arch: TargetArch) -> Result<BpfMap> { |
390 | | // Run the frontend. |
391 | | let seccomp_filters: HashMap<String, SeccompFilter> = |
392 | | JsonCompiler::new(arch).compile(reader)?; |
393 | | |
394 | | // Run the backend. |
395 | | let mut bpf_data: BpfMap = BpfMap::with_capacity(seccomp_filters.len()); |
396 | | for (name, seccomp_filter) in seccomp_filters { |
397 | | bpf_data.insert(name, seccomp_filter.try_into()?); |
398 | | } |
399 | | |
400 | | Ok(bpf_data) |
401 | | } |