Skip to content

Commit 762bb6a

Browse files
committed
Add support for seccomp thread sync feature
- Adds SeccompFlag and SeccompFlagset with initial support for just SyncThreads/SECCOMP_FILTER_FLAG_TSYNC - Adds public functions `seccompiler::apply_filter_all_threads` and `apply_filter_with_flags` - Moves the body of apply_filter into apply_filter_with_flags Resolves #57 Signed-off-by: Harry Stern <[email protected]>
1 parent 83dcac7 commit 762bb6a

File tree

6 files changed

+197
-5
lines changed

6 files changed

+197
-5
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Upcoming Release
2+
3+
- Add `apply_filter_with_flags` to pass seccomp flags
4+
- Add SeccompFlag and SeccompFlagset
5+
- Add `apply_filter_all_threads` convenience function
6+
17
# v0.3.0
28

39
## Changed

coverage_config_x86_64.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"coverage_score": 93.6,
2+
"coverage_score": 93.0,
33
"exclude_path": "tests/integration_tests.rs,tests/json.rs",
44
"crate_features": "json"
55
}

src/backend/flags.rs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use std::ops::{BitOr, BitOrAssign};
2+
3+
#[derive(Copy, Clone, Debug)]
4+
/// A flag accepted by the `seccomp` syscall.
5+
pub enum SeccompFlag {
6+
/// Synchronize the current filter across all threads in the process.
7+
ThreadSync,
8+
// TODO: other flags
9+
}
10+
11+
impl SeccompFlag {
12+
fn as_u64(&self) -> u64 {
13+
// Values given in linux/seccomp.h
14+
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/seccomp.h
15+
match self {
16+
SeccompFlag::ThreadSync => libc::SECCOMP_FILTER_FLAG_TSYNC,
17+
}
18+
}
19+
20+
/// Convert this flag to a new [`SeccompFlagset`] containing it.
21+
pub fn to_flagset(self) -> SeccompFlagset {
22+
SeccompFlagset(self.as_u64())
23+
}
24+
}
25+
26+
#[derive(Copy, Clone, Debug, Default)]
27+
/// A set of [`SeccompFlag`]s to be passed to the `seccomp` syscall.
28+
pub struct SeccompFlagset(u64);
29+
30+
impl SeccompFlagset {
31+
/// Create a new, empty SeccompFlagset.
32+
pub fn new() -> SeccompFlagset {
33+
SeccompFlagset(0)
34+
}
35+
36+
/// Return a u64 representing the flags to be passed to the seccomp syscall.
37+
pub fn as_u64(&self) -> u64 {
38+
self.0
39+
}
40+
}
41+
42+
impl BitOr for SeccompFlag {
43+
type Output = SeccompFlagset;
44+
45+
fn bitor(self, rhs: Self) -> Self::Output {
46+
SeccompFlagset(self.as_u64() | rhs.as_u64())
47+
}
48+
}
49+
50+
impl BitOr for SeccompFlagset {
51+
type Output = SeccompFlagset;
52+
53+
fn bitor(self, rhs: SeccompFlagset) -> Self::Output {
54+
SeccompFlagset(self.0 | rhs.0)
55+
}
56+
}
57+
58+
impl BitOrAssign for SeccompFlagset {
59+
fn bitor_assign(&mut self, rhs: SeccompFlagset) {
60+
self.0 |= rhs.0;
61+
}
62+
}
63+
64+
#[cfg(test)]
65+
mod tests {
66+
use super::*;
67+
68+
#[test]
69+
/// Test all the bitwise or options for SeccompFlag and SeccompFlagset
70+
fn test_bitwise() {
71+
let mut flagset = SeccompFlag::ThreadSync | SeccompFlag::ThreadSync;
72+
assert_eq!(flagset.as_u64(), 1);
73+
flagset |= SeccompFlag::ThreadSync.to_flagset() | SeccompFlag::ThreadSync.to_flagset();
74+
assert_eq!(flagset.as_u64(), 1);
75+
}
76+
}

src/backend/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
mod bpf;
88
mod condition;
99
mod filter;
10+
mod flags;
1011
mod rule;
1112

1213
pub use condition::SeccompCondition;
@@ -27,6 +28,8 @@ use bpf::{
2728

2829
pub use bpf::{sock_filter, BpfProgram, BpfProgramRef};
2930

31+
pub use flags::{SeccompFlag, SeccompFlagset};
32+
3033
/// Backend Result type.
3134
type Result<T> = std::result::Result<T, Error>;
3235

src/lib.rs

+33-4
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ use frontend::json::{Error as JsonFrontendError, JsonCompiler};
205205
// Re-export the IR public types.
206206
pub use backend::{
207207
sock_filter, BpfProgram, BpfProgramRef, Error as BackendError, SeccompAction, SeccompCmpArgLen,
208-
SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule, TargetArch,
208+
SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompFlag, SeccompFlagset, SeccompRule,
209+
TargetArch,
209210
};
210211

211212
// BPF structure definition for filter array.
@@ -292,6 +293,30 @@ impl From<JsonFrontendError> for Error {
292293
///
293294
/// [`BpfProgram`]: type.BpfProgram.html
294295
pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
296+
apply_filter_with_flags(bpf_filter, SeccompFlagset::new())
297+
}
298+
299+
/// Apply a BPF filter to the all threads in the process.
300+
///
301+
/// # Arguments
302+
///
303+
/// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed.
304+
///
305+
/// [`BpfProgram`]: type.BpfProgram.html
306+
pub fn apply_filter_all_threads(bpf_filter: BpfProgramRef) -> Result<()> {
307+
apply_filter_with_flags(bpf_filter, SeccompFlag::ThreadSync.to_flagset())
308+
}
309+
310+
/// Apply a BPF filter to the calling thread.
311+
///
312+
/// # Arguments
313+
///
314+
/// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed.
315+
/// * `flags` - A u64 representing a bitset of flags, typically created by or-ing [`SeccompFlags`]
316+
/// together
317+
///
318+
/// [`BpfProgram`]: type.BpfProgram.html
319+
pub fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: SeccompFlagset) -> Result<()> {
295320
// If the program is empty, don't install the filter.
296321
if bpf_filter.is_empty() {
297322
return Err(Error::EmptyFilter);
@@ -310,13 +335,17 @@ pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
310335
};
311336
let bpf_prog_ptr = &bpf_prog as *const sock_fprog;
312337

338+
// Until https://github.com/rust-lang/libc/issues/3342 is fixed, define locally
339+
const SECCOMP_SET_MODE_FILTER: libc::c_int = 1;
340+
313341
// SAFETY:
314342
// Safe because the kernel performs a `copy_from_user` on the filter and leaves the memory
315343
// untouched. We can therefore use a reference to the BpfProgram, without needing ownership.
316344
let rc = unsafe {
317-
libc::prctl(
318-
libc::PR_SET_SECCOMP,
319-
libc::SECCOMP_MODE_FILTER,
345+
libc::syscall(
346+
libc::SYS_seccomp,
347+
SECCOMP_SET_MODE_FILTER,
348+
flags.as_u64(),
320349
bpf_prog_ptr,
321350
)
322351
};

tests/multi_thread.rs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#![allow(clippy::undocumented_unsafe_blocks)]
2+
3+
/// This test is in a separate top-level test file so that it is isolated from the other tests -
4+
/// each file in the tests/ directory gets compiled to a separate binary and is run as a separate
5+
/// process.
6+
use std::collections::BTreeMap;
7+
8+
use std::sync::mpsc::sync_channel;
9+
use std::thread;
10+
11+
use seccompiler::{
12+
apply_filter_all_threads, BpfProgram, SeccompAction, SeccompFilter, SeccompRule,
13+
};
14+
use std::env::consts::ARCH;
15+
16+
#[test]
17+
/// Test seccomp's TSYNC functionality, which syncs the current filter to all threads in the
18+
/// process.
19+
fn test_tsync() {
20+
// These channels will block on send until the receiver has called recv.
21+
let (setup_tx, setup_rx) = sync_channel::<()>(0);
22+
let (finish_tx, finish_rx) = sync_channel::<()>(0);
23+
24+
let seccomp_thread = thread::spawn(move || {
25+
let rules = vec![(libc::SYS_getpid, vec![])];
26+
27+
let rule_map: BTreeMap<i64, Vec<SeccompRule>> = rules.into_iter().collect();
28+
29+
// Build seccomp filter only disallowing getpid
30+
let filter = SeccompFilter::new(
31+
rule_map,
32+
SeccompAction::Allow,
33+
SeccompAction::Errno(1u32),
34+
ARCH.try_into().unwrap(),
35+
)
36+
.unwrap();
37+
38+
let filter: BpfProgram = filter.try_into().unwrap();
39+
apply_filter_all_threads(&filter).unwrap();
40+
41+
// seccomp setup done, let the other thread start
42+
setup_tx.send(()).unwrap();
43+
44+
// don't close this thread until the other thread is done asserting. This way we can be
45+
// sure the thread that loaded the filter is definitely active when the other thread runs.
46+
finish_rx.recv().unwrap();
47+
println!("exit seccomp thread");
48+
});
49+
50+
let test_thread = thread::spawn(move || {
51+
// wait until seccomp setup is done
52+
setup_rx.recv().unwrap();
53+
54+
// try to use getpid, which we have disallowed on another thread
55+
let pid = unsafe { libc::getpid() };
56+
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
57+
58+
assert_eq!(pid, -1, "getpid should return -1 as set in SeccompFilter");
59+
assert_eq!(errno, 0, "there should be no errors");
60+
61+
// let other thread know we've passed
62+
finish_tx.send(()).unwrap();
63+
println!("exit io thread");
64+
});
65+
66+
let seccomp_res = seccomp_thread.join();
67+
assert!(
68+
seccomp_res.is_ok(),
69+
"seccomp thread failed: {:?}",
70+
seccomp_res.unwrap_err()
71+
);
72+
let test_res = test_thread.join();
73+
assert!(
74+
test_res.is_ok(),
75+
"test thread failed: {:?}",
76+
test_res.unwrap_err()
77+
);
78+
}

0 commit comments

Comments
 (0)