Skip to content

Commit 184f2d6

Browse files
boustrophedonpetreeftime
authored andcommitted
Add support for seccomp thread sync feature
- Adds public functions `seccompiler::apply_filter_all_threads` and private `apply_filter_with_flags` - Moves the body of apply_filter into apply_filter_with_flags - Uses seccomp call directly in apply_filter, so new Error variant is added. - Error variant also added for TSYNC failures Resolves #57 Signed-off-by: Harry Stern <[email protected]>
1 parent 83dcac7 commit 184f2d6

5 files changed

+165
-8
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# Upcoming Release
2+
3+
- Seccomp is now activated via the seccomp syscall, not prctl
4+
- A new Error::Seccomp variant is added to indictate seccomp syscall failures
5+
- Add `apply_filter_all_threads` convenience function which uses the seccomp
6+
TSYNC feature to synchronize all threads in the process to the same filter
7+
- A new Error::ThreadSync variant is added to indicate failure to sync threads
8+
19
# v0.3.0
210

311
## 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/lib.rs

+57-5
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ pub use backend::{
208208
SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule, TargetArch,
209209
};
210210

211+
// Until https://github.com/rust-lang/libc/issues/3342 is fixed, define locally
212+
// From <linux/seccomp.h>
213+
const SECCOMP_SET_MODE_FILTER: libc::c_int = 1;
214+
211215
// BPF structure definition for filter array.
212216
// See /usr/include/linux/filter.h .
213217
#[repr(C)]
@@ -231,6 +235,11 @@ pub enum Error {
231235
EmptyFilter,
232236
/// System error related to calling `prctl`.
233237
Prctl(io::Error),
238+
/// System error related to calling `seccomp` syscall.
239+
Seccomp(io::Error),
240+
/// Returned when calling `seccomp` with the thread sync flag (TSYNC) fails. Contains the pid
241+
/// of the thread that caused the failure.
242+
ThreadSync(libc::c_long),
234243
/// Json Frontend Error.
235244
#[cfg(feature = "json")]
236245
JsonFrontend(JsonFrontendError),
@@ -243,6 +252,8 @@ impl std::error::Error for Error {
243252
match self {
244253
Backend(error) => Some(error),
245254
Prctl(error) => Some(error),
255+
Seccomp(error) => Some(error),
256+
ThreadSync(_) => None,
246257
#[cfg(feature = "json")]
247258
JsonFrontend(error) => Some(error),
248259
_ => None,
@@ -264,6 +275,16 @@ impl Display for Error {
264275
Prctl(errno) => {
265276
write!(f, "Error calling `prctl`: {}", errno)
266277
}
278+
Seccomp(errno) => {
279+
write!(f, "Error calling `seccomp`: {}", errno)
280+
}
281+
ThreadSync(pid) => {
282+
write!(
283+
f,
284+
"Seccomp filter synchronization failed in thread `{}`",
285+
pid
286+
)
287+
}
267288
#[cfg(feature = "json")]
268289
JsonFrontend(error) => {
269290
write!(f, "Json Frontend error: {}", error)
@@ -292,6 +313,30 @@ impl From<JsonFrontendError> for Error {
292313
///
293314
/// [`BpfProgram`]: type.BpfProgram.html
294315
pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
316+
apply_filter_with_flags(bpf_filter, 0)
317+
}
318+
319+
/// Apply a BPF filter to the all threads in the process via the TSYNC feature. Please read the
320+
/// man page for seccomp (`man 2 seccomp`) for more information.
321+
///
322+
/// # Arguments
323+
///
324+
/// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed.
325+
///
326+
/// [`BpfProgram`]: type.BpfProgram.html
327+
pub fn apply_filter_all_threads(bpf_filter: BpfProgramRef) -> Result<()> {
328+
apply_filter_with_flags(bpf_filter, libc::SECCOMP_FILTER_FLAG_TSYNC)
329+
}
330+
331+
/// Apply a BPF filter to the calling thread.
332+
///
333+
/// # Arguments
334+
///
335+
/// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed.
336+
/// * `flags` - A u64 representing a bitset of seccomp's flags parameter.
337+
///
338+
/// [`BpfProgram`]: type.BpfProgram.html
339+
fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: libc::c_ulong) -> Result<()> {
295340
// If the program is empty, don't install the filter.
296341
if bpf_filter.is_empty() {
297342
return Err(Error::EmptyFilter);
@@ -314,14 +359,21 @@ pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
314359
// Safe because the kernel performs a `copy_from_user` on the filter and leaves the memory
315360
// untouched. We can therefore use a reference to the BpfProgram, without needing ownership.
316361
let rc = unsafe {
317-
libc::prctl(
318-
libc::PR_SET_SECCOMP,
319-
libc::SECCOMP_MODE_FILTER,
362+
libc::syscall(
363+
libc::SYS_seccomp,
364+
SECCOMP_SET_MODE_FILTER,
365+
flags,
320366
bpf_prog_ptr,
321367
)
322368
};
323-
if rc != 0 {
324-
return Err(Error::Prctl(io::Error::last_os_error()));
369+
370+
#[allow(clippy::comparison_chain)]
371+
// Per manpage, if TSYNC fails, retcode is >0 and equals the pid of the thread that caused the
372+
// failure. Otherwise, error code is -1 and errno is set.
373+
if rc < 0 {
374+
return Err(Error::Seccomp(io::Error::last_os_error()));
375+
} else if rc > 0 {
376+
return Err(Error::ThreadSync(rc));
325377
}
326378

327379
Ok(())

tests/integration_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ fn test_filter_apply() {
714714
// Apply seccomp filter.
715715
assert!(matches!(
716716
apply_filter(&filter).unwrap_err(),
717-
Error::Prctl(_)
717+
Error::Seccomp(_)
718718
));
719719
})
720720
.join()
@@ -756,7 +756,7 @@ fn test_filter_apply() {
756756

757757
assert!(matches!(
758758
apply_filter(&filter).unwrap_err(),
759-
Error::Prctl(_)
759+
Error::Seccomp(_)
760760
));
761761

762762
// test that seccomp level remains 0 on failure.

tests/multi_thread.rs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
fn check_getpid_fails() {
17+
let pid = unsafe { libc::getpid() };
18+
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
19+
20+
assert_eq!(pid, -1, "getpid should return -1 as set in SeccompFilter");
21+
assert_eq!(errno, 0, "there should be no errors");
22+
}
23+
24+
#[test]
25+
/// Test seccomp's TSYNC functionality, which syncs the current filter to all threads in the
26+
/// process.
27+
fn test_tsync() {
28+
// These channels will block on send until the receiver has called recv.
29+
let (setup_tx, setup_rx) = sync_channel::<()>(0);
30+
let (finish_tx, finish_rx) = sync_channel::<()>(0);
31+
32+
// first check getpid is working
33+
let pid = unsafe { libc::getpid() };
34+
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
35+
36+
assert!(pid > 0, "getpid should return the actual pid");
37+
assert_eq!(errno, 0, "there should be no errors");
38+
39+
// create two threads, one which applies the filter to all threads and another which tries
40+
// to call getpid.
41+
let seccomp_thread = thread::spawn(move || {
42+
let rules = vec![(libc::SYS_getpid, vec![])];
43+
44+
let rule_map: BTreeMap<i64, Vec<SeccompRule>> = rules.into_iter().collect();
45+
46+
// Build seccomp filter only disallowing getpid
47+
let filter = SeccompFilter::new(
48+
rule_map,
49+
SeccompAction::Allow,
50+
SeccompAction::Errno(1u32),
51+
ARCH.try_into().unwrap(),
52+
)
53+
.unwrap();
54+
55+
let filter: BpfProgram = filter.try_into().unwrap();
56+
apply_filter_all_threads(&filter).unwrap();
57+
58+
// Verify seccomp is working in this thread
59+
check_getpid_fails();
60+
61+
// seccomp setup done, let the other thread start
62+
setup_tx.send(()).unwrap();
63+
64+
// don't close this thread until the other thread is done asserting. This way we can be
65+
// sure the thread that loaded the filter is definitely active when the other thread runs.
66+
finish_rx.recv().unwrap();
67+
println!("exit seccomp thread");
68+
});
69+
70+
let test_thread = thread::spawn(move || {
71+
// wait until seccomp setup is done
72+
setup_rx.recv().unwrap();
73+
74+
// Verify seccomp is working in this thread after disallowing it in other thread
75+
check_getpid_fails();
76+
77+
// let other thread know we've passed
78+
finish_tx.send(()).unwrap();
79+
println!("exit io thread");
80+
});
81+
82+
let seccomp_res = seccomp_thread.join();
83+
assert!(
84+
seccomp_res.is_ok(),
85+
"seccomp thread failed: {:?}",
86+
seccomp_res.unwrap_err()
87+
);
88+
let test_res = test_thread.join();
89+
assert!(
90+
test_res.is_ok(),
91+
"test thread failed: {:?}",
92+
test_res.unwrap_err()
93+
);
94+
95+
// Verify seccomp is working in the parent thread as well
96+
check_getpid_fails();
97+
}

0 commit comments

Comments
 (0)