Skip to content

Commit

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

- Add `apply_filter_with_flags` to pass seccomp flags
- Add SeccompFlag and SeccompFlagset
- Add `apply_filter_all_threads` convenience function

# v0.3.0

## Changed
Expand Down
76 changes: 76 additions & 0 deletions src/backend/flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::ops::{BitOr, BitOrAssign};

#[derive(Copy, Clone, Debug)]
/// A flag accepted by the `seccomp` syscall.
pub enum SeccompFlag {
/// Synchronize the current filter across all threads in the process.
ThreadSync,
// TODO: other flags
}

impl SeccompFlag {
fn as_u64(&self) -> u64 {
// Values given in linux/seccomp.h
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/seccomp.h
match self {
SeccompFlag::ThreadSync => 1,
}
}

/// Convert this flag to a new [`SeccompFlagset`] containing it.
pub fn to_flagset(self) -> SeccompFlagset {
SeccompFlagset(self.as_u64())
}
}

#[derive(Copy, Clone, Debug, Default)]
/// A set of [`SeccompFlag`]s to be passed to the `seccomp` syscall.
pub struct SeccompFlagset(u64);

impl SeccompFlagset {
/// Create a new, empty SeccompFlagset.
pub fn new() -> SeccompFlagset {
SeccompFlagset(0)
}

/// Return a u64 representing the flags to be passed to the seccomp syscall.
pub fn as_u64(&self) -> u64 {
self.0
}
}

impl BitOr for SeccompFlag {
type Output = SeccompFlagset;

fn bitor(self, rhs: Self) -> Self::Output {
SeccompFlagset(self.as_u64() | rhs.as_u64())
}
}

impl BitOr for SeccompFlagset {
type Output = SeccompFlagset;

fn bitor(self, rhs: SeccompFlagset) -> Self::Output {
SeccompFlagset(self.0 | rhs.0)
}
}

impl BitOrAssign for SeccompFlagset {
fn bitor_assign(&mut self, rhs: SeccompFlagset) {
self.0 |= rhs.0;
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
/// Test all the bitwise or options for SeccompFlag and SeccompFlagset
fn test_bitwise() {
let mut flagset = SeccompFlag::ThreadSync | SeccompFlag::ThreadSync;
assert_eq!(flagset.as_u64(), 1);
flagset |= SeccompFlag::ThreadSync.to_flagset() | SeccompFlag::ThreadSync.to_flagset();
assert_eq!(flagset.as_u64(), 1);
}
}
3 changes: 3 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
mod bpf;
mod condition;
mod filter;
mod flags;
mod rule;

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

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

pub use flags::{SeccompFlag, SeccompFlagset};

/// Backend Result type.
type Result<T> = std::result::Result<T, Error>;

Expand Down
34 changes: 30 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ use frontend::json::{Error as JsonFrontendError, JsonCompiler};
// Re-export the IR public types.
pub use backend::{
sock_filter, BpfProgram, BpfProgramRef, Error as BackendError, SeccompAction, SeccompCmpArgLen,
SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule, TargetArch,
SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompFlag, SeccompFlagset, SeccompRule,
TargetArch,
};

// BPF structure definition for filter array.
Expand Down Expand Up @@ -292,6 +293,30 @@ impl From<JsonFrontendError> for Error {
///
/// [`BpfProgram`]: type.BpfProgram.html
pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
apply_filter_with_flags(bpf_filter, SeccompFlagset::new())
}

/// Apply a BPF filter to the all threads in the process.
///
/// # 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, SeccompFlag::ThreadSync.to_flagset())
}

/// 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 flags, typically created by or-ing [`SeccompFlags`]
/// together
///
/// [`BpfProgram`]: type.BpfProgram.html
pub fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: SeccompFlagset) -> Result<()> {
// If the program is empty, don't install the filter.
if bpf_filter.is_empty() {
return Err(Error::EmptyFilter);
Expand All @@ -314,9 +339,10 @@ pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> {
// 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,
libc::SECCOMP_SET_MODE_FILTER,
flags.as_u64(),
bpf_prog_ptr,
)
};
Expand Down
78 changes: 78 additions & 0 deletions tests/multi_thread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#![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;

#[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();

// 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();

// try to use getpid, which we have disallowed on another thread
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");

// 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()
);
}

0 comments on commit c63ac88

Please sign in to comment.