Coverage Report

Created: 2026-06-30 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}