Skip to content

Commit

Permalink
Add support for seccomp thread sync feature
Browse files Browse the repository at this point in the history
- 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]>
  • Loading branch information
boustrophedon committed Sep 7, 2023
1 parent 83dcac7 commit fd551f2
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 8 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Upcoming Release

- Seccomp is now activated via the seccomp syscall, not prctl
- A new Error::Seccomp variant is added to indictate seccomp syscall failures
- Add `apply_filter_all_threads` convenience function which uses the seccomp
TSYNC feature to synchronize all threads in the process to the same filter
- A new Error::ThreadSync variant is added to indicate failure to sync threads

# v0.3.0

## Changed
Expand Down
2 changes: 1 addition & 1 deletion coverage_config_x86_64.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"coverage_score": 93.6,
"coverage_score": 93.0,
"exclude_path": "tests/integration_tests.rs,tests/json.rs",
"crate_features": "json"
}
62 changes: 57 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ pub use backend::{
SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule, TargetArch,
};

// From <linux/seccomp.h>
const SECCOMP_SET_MODE_FILTER: libc::c_int = 1;

// BPF structure definition for filter array.
// See /usr/include/linux/filter.h .
#[repr(C)]
Expand All @@ -231,6 +234,11 @@ pub enum Error {
EmptyFilter,
/// System error related to calling `prctl`.
Prctl(io::Error),
/// System error related to calling `seccomp` syscall.
Seccomp(io::Error),
/// Returned when calling `seccomp` with the thread sync flag (TSYNC) fails. Contains the pid
/// of the thread that caused the failure.
ThreadSync(libc::c_long),
/// Json Frontend Error.
#[cfg(feature = "json")]
JsonFrontend(JsonFrontendError),
Expand All @@ -243,6 +251,8 @@ impl std::error::Error for Error {
match self {
Backend(error) => Some(error),
Prctl(error) => Some(error),
Seccomp(error) => Some(error),
ThreadSync(_) => None,
#[cfg(feature = "json")]
JsonFrontend(error) => Some(error),
_ => None,
Expand All @@ -264,6 +274,16 @@ impl Display for Error {
Prctl(errno) => {
write!(f, "Error calling `prctl`: {}", errno)
}
Seccomp(errno) => {
write!(f, "Error calling `seccomp`: {}", errno)
}
ThreadSync(pid) => {
write!(
f,
"Seccomp filter synchronization failed in thread `{}`",
pid
)
}
#[cfg(feature = "json")]
JsonFrontend(error) => {
write!(f, "Json Frontend error: {}", error)
Expand Down Expand Up @@ -292,6 +312,30 @@ impl From<JsonFrontendError> for Error {
///
/// [`BpfProgram`]: type.BpfProgram.html
pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
apply_filter_with_flags(bpf_filter, 0)
}

/// Apply a BPF filter to the all threads in the process via the TSYNC feature. Please read the
/// man page for seccomp (`man 2 seccomp`) for more information.
///
/// # Arguments
///
/// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed.
///
/// [`BpfProgram`]: type.BpfProgram.html
pub fn apply_filter_all_threads(bpf_filter: BpfProgramRef) -> Result<()> {
apply_filter_with_flags(bpf_filter, libc::SECCOMP_FILTER_FLAG_TSYNC)
}

/// Apply a BPF filter to the calling thread.
///
/// # Arguments
///
/// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed.
/// * `flags` - A u64 representing a bitset of seccomp's flags parameter.
///
/// [`BpfProgram`]: type.BpfProgram.html
fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: libc::c_ulong) -> Result<()> {
// If the program is empty, don't install the filter.
if bpf_filter.is_empty() {
return Err(Error::EmptyFilter);
Expand All @@ -310,18 +354,26 @@ pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
};
let bpf_prog_ptr = &bpf_prog as *const sock_fprog;

// Until https://github.com/rust-lang/libc/issues/3342 is fixed, define locally

// SAFETY:
// Safe because the kernel performs a `copy_from_user` on the filter and leaves the memory
// untouched. We can therefore use a reference to the BpfProgram, without needing ownership.
let rc = unsafe {
libc::prctl(
libc::PR_SET_SECCOMP,
libc::SECCOMP_MODE_FILTER,
libc::syscall(
libc::SYS_seccomp,
SECCOMP_SET_MODE_FILTER,
flags,
bpf_prog_ptr,
)
};
if rc != 0 {
return Err(Error::Prctl(io::Error::last_os_error()));

// Per manpage, if TSYNC fails, retcode is >0 and equals the pid of the thread that caused the
// failure. Otherwise, error code is -1 and errno is set.
if rc == -1 {
return Err(Error::Seccomp(io::Error::last_os_error()));
} else if rc > 0 {
return Err(Error::ThreadSync(rc));
}

Ok(())
Expand Down
4 changes: 2 additions & 2 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ fn test_filter_apply() {
// Apply seccomp filter.
assert!(matches!(
apply_filter(&filter).unwrap_err(),
Error::Prctl(_)
Error::Seccomp(_)
));
})
.join()
Expand Down Expand Up @@ -756,7 +756,7 @@ fn test_filter_apply() {

assert!(matches!(
apply_filter(&filter).unwrap_err(),
Error::Prctl(_)
Error::Seccomp(_)
));

// test that seccomp level remains 0 on failure.
Expand Down
88 changes: 88 additions & 0 deletions tests/multi_thread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#![allow(clippy::undocumented_unsafe_blocks)]

/// This test is in a separate top-level test file so that it is isolated from the other tests -
/// each file in the tests/ directory gets compiled to a separate binary and is run as a separate
/// process.
use std::collections::BTreeMap;

use std::sync::mpsc::sync_channel;
use std::thread;

use seccompiler::{
apply_filter_all_threads, BpfProgram, SeccompAction, SeccompFilter, SeccompRule,
};
use std::env::consts::ARCH;

fn check_getpid_fails() {
let pid = unsafe { libc::getpid() };
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();

assert_eq!(pid, -1, "getpid should return -1 as set in SeccompFilter");
assert_eq!(errno, 0, "there should be no errors");
}

#[test]
/// Test seccomp's TSYNC functionality, which syncs the current filter to all threads in the
/// process.
fn test_tsync() {
// These channels will block on send until the receiver has called recv.
let (setup_tx, setup_rx) = sync_channel::<()>(0);
let (finish_tx, finish_rx) = sync_channel::<()>(0);

let seccomp_thread = thread::spawn(move || {
let rules = vec![(libc::SYS_getpid, vec![])];

let rule_map: BTreeMap<i64, Vec<SeccompRule>> = rules.into_iter().collect();

// Build seccomp filter only disallowing getpid
let filter = SeccompFilter::new(
rule_map,
SeccompAction::Allow,
SeccompAction::Errno(1u32),
ARCH.try_into().unwrap(),
)
.unwrap();

let filter: BpfProgram = filter.try_into().unwrap();
apply_filter_all_threads(&filter).unwrap();

// Verify seccomp is working in this thread
check_getpid_fails();

// seccomp setup done, let the other thread start
setup_tx.send(()).unwrap();

// don't close this thread until the other thread is done asserting. This way we can be
// sure the thread that loaded the filter is definitely active when the other thread runs.
finish_rx.recv().unwrap();
println!("exit seccomp thread");
});

let test_thread = thread::spawn(move || {
// wait until seccomp setup is done
setup_rx.recv().unwrap();

// Verify seccomp is working in this thread after disallowing it in other thread
check_getpid_fails();

// let other thread know we've passed
finish_tx.send(()).unwrap();
println!("exit io thread");
});

let seccomp_res = seccomp_thread.join();
assert!(
seccomp_res.is_ok(),
"seccomp thread failed: {:?}",
seccomp_res.unwrap_err()
);
let test_res = test_thread.join();
assert!(
test_res.is_ok(),
"test thread failed: {:?}",
test_res.unwrap_err()
);

// Verify seccomp is working in the parent thread as well
check_getpid_fails();
}

0 comments on commit fd551f2

Please sign in to comment.