Skip to content
Open
164 changes: 164 additions & 0 deletions src/builtins/allow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//! The `allow!`-macro to generate `allow_`-methods.

/// Generate a method-call chain of outer methods.
///
/// Note: This macro will not check,
/// whether dangerous methods are declared inside non-dangerous methods!
macro_rules! __allow_chain {
// Without `unsafe`: Just call `$method()`.
(
$self:expr =>
$(#[$_attr:meta])*
$_vis:vis fn $method:ident($($_syscall:ident)?)
$($rest:tt)*
) => {
__allow_chain! { $self.$method() => $($rest)* }
};

// With `unsafe`: An `yes_really()` is required.
(
$self:expr =>
$(#[$_attr:meta])*
$_vis:vis unsafe fn $method:ident($($_syscall:ident)?)
$($rest:tt)*
) => {
__allow_chain! { $self.$method().yes_really() => $($rest)* }
};

// Ignore trailing `;` and inner declaration `{ … }`:
( $self:expr => ; $($rest:tt)* ) => { __allow_chain! { $self => $($rest)* } };
( $self:expr => { $($_inner:tt)* } $($rest:tt)* ) => { __allow_chain! { $self => $($rest)* } };

// Nothing to match, processing the body of `__allow_chain!` is done.
( $self:expr => ) => { $self };
}

/// This is the internal implementation detail of [`allow!`].
///
/// This macro is necessary, because the outer-most methods of `allow!` might be dangerous and
/// whether or not the declaration of dangerous methods is allowed is indicated by `@dangerous`.
macro_rules! __allow {
// Declare a method, that allows a single syscall.
//
// The generated methods returns `Self`.
(
$(@$dangerous:ident)?
$(#[$attr:meta])*
$vis:vis fn $method:ident($syscall:ident);

$($rest:tt)*
) => {
// Declare the method:
$(#[$attr])*
$vis fn $method(mut self) -> Self {
let _ = self.syscalls.insert(syscalls::Sysno::$syscall);
self
}

// Parse the rest:
__allow! { $(@$dangerous)? $($rest)* }
};

// Declare a method, that allows multiple syscalls.
//
// The generated methods returns `Self`.
(
$(@$dangerous:ident)?
$(#[$attr:meta])*
$vis:vis fn $method:ident() {
$($inner:tt)*
}

$($rest:tt)*
) => {
// Declare the outer method:
$(#[$attr])*
$vis fn $method(self) -> Self {
__allow_chain! { self => $($inner)* }
}

// Parse the inner methods. They must not be dangerous:
__allow! { $($inner)* }

// Parse the rest:
__allow! { $(@$dangerous)? $($rest)* }
};

// Declare a dangerous method, that allows a single syscall.
//
// The generated outer method returns `YesReally<Self>`.
//
// The label `@dangerous` ensures,
// that dangerous inner methods can only be declared inside dangerous outer methods.
(
@dangerous
$(#[$attr:meta])*
$vis:vis unsafe fn $method:ident($syscall:ident);

$($rest:tt)*
) => {
// Declare the method:
$(#[$attr])*
$vis fn $method(mut self) -> YesReally<Self> {
let _ = self.syscalls.insert(syscalls::Sysno::$syscall);
YesReally::new(self)
}

// Parse the rest:
__allow! { @dangerous $($rest)* }
};

// Declare a dangerous method, that allows multiple syscalls.
//
// The generated outer method returns `YesReally<Self>`.
//
// The label `@dangerous` ensures,
// that dangerous inner methods can only be declared inside dangerous outer methods.
(
@dangerous
$(#[$attr:meta])*
$vis:vis unsafe fn $method:ident() {
$($inner:tt)*
}

$($rest:tt)*
) => {
// Declare the outer method:
$(#[$attr])*
$vis fn $method(self) -> YesReally<Self> {
YesReally::new(__allow_chain! { self => $($inner)* })
}

// Parse the inner methods. They might or might not be dangerous:
__allow! { @dangerous $($inner)* }

// Parse the rest:
__allow! { @dangerous $($rest)* }
};

// Nothing to match, processing the body of `__allow!` is done.
( $(@dangerous)? ) => {};
}

/// Implement `allow_*`-methods.
///
/// The syntax is similar to the usual syntax of function signatures,
/// including documentation, attributes and visibility.
/// However, there are some differences:
///
/// * All methods declared inside a `allow! { … }` block have one or zero arguments:
/// If a method has an argument, it is the name of the syscall,
/// which will be concatenated to `syscalls::Sysno::`.
/// * If the argument is a syscall, the declaration must end with a semicolon,
/// otherwise, the declaration must end with block (`{ … }`)
/// with inner method-declarations.
/// * Methods, that are dangerous and should be confirmed with `yes_really`,
/// can be declared by putting the keyword `unsafe` in front of the `fn`.
/// * Inner dangerous methods can be declared as the outer-most methods
/// and inside a dangerous outer method only.
///
/// See e.g. `time.rs` and `user_id.rs`.
macro_rules! allow {
// The outer-most methods can be dangerous.
($($tokens:tt)*) => { __allow! { @dangerous $( $tokens )* } };
}
40 changes: 40 additions & 0 deletions src/builtins/kill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Allow `sys_kill`.

use {crate::RuleSet, syscalls::Sysno};

/// Allow the syscall `kill` to send signals to other processes.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd)]
#[must_use]
pub struct Kill;

impl RuleSet for Kill {
fn simple_rules(&self) -> Vec<Sysno> {
Vec::from([Sysno::kill])
}

fn name(&self) -> &'static str {
"Kill"
}
}

#[cfg(test)]
mod tests {
use {super::Kill, crate::RuleSet as _, syscalls::Sysno};

#[test]
fn name() {
assert_eq!(Kill.name(), "Kill");
}

#[test]
fn simple_rules() {
let rules = Kill.simple_rules();
assert_eq!(rules.len(), 1);
assert!(rules.contains(&Sysno::kill));
}

#[test]
fn conditional_rules() {
assert!(Kill.conditional_rules().is_empty());
}
}
42 changes: 24 additions & 18 deletions src/builtins/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
//! Built-in [`RuleSet`](crate::RuleSet)s

#[macro_use]
pub mod allow;

pub mod basic;
pub mod danger_zone;
pub mod kill;
pub mod network;
pub mod pipes;
pub mod systemio;
pub mod time;
pub mod truncate;
pub mod user_id;

pub use self::{
basic::BasicCapabilities,
kill::Kill,
network::{Networking, Netlink, SocketPair},
systemio::SystemIO,
time::Time,
truncate::Truncate,
user_id::UserId,
};

/// A struct whose purpose is to make you read the documentation for the function you're calling.
/// If you're reading this, go read the documentation for the function that is returning this
/// object.
Expand All @@ -16,23 +39,6 @@ impl<T> YesReally<T> {

/// Make a [`YesReally`].
pub fn new(inner: T) -> YesReally<T> {
YesReally {
inner,
}
YesReally { inner }
}
}

pub mod basic;
pub use basic::BasicCapabilities;

pub mod systemio;
pub use systemio::SystemIO;

pub mod network;
pub use network::Networking;

pub mod time;
pub use time::Time;

pub mod danger_zone;
pub mod pipes;
15 changes: 10 additions & 5 deletions src/builtins/network.rs → src/builtins/network/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
//! Contains a [`RuleSet`] for allowing networking-related syscalls.

use std::collections::{HashMap, HashSet};
pub mod netlink;
pub mod socket_pair;

use syscalls::Sysno;
use {
super::YesReally,
crate::{RuleSet, SeccompRule},
std::collections::{HashMap, HashSet},
syscalls::Sysno,
};

use super::YesReally;
use crate::{SeccompRule, RuleSet};
pub use self::{netlink::Netlink, socket_pair::SocketPair};

// TODO: make bind calls conditional on the DGRAM/UNIX/STREAM flag in each function

Expand Down Expand Up @@ -205,7 +210,7 @@ impl Networking {
self.custom.entry(Sysno::socket)
.or_insert_with(Vec::new)
.push(rule);

self.allowed.extend(&[Sysno::connect]);
self.allowed.extend(NET_IO_SYSCALLS);
self.allowed.extend(NET_READ_SYSCALLS);
Expand Down
62 changes: 62 additions & 0 deletions src/builtins/network/netlink.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! Allow netlink-sockets.

use {
crate::{
RuleSet, SeccompArgumentFilter as Filter, SeccompRule as Rule,
SeccompilerComparator as Comparator,
},
std::collections::HashMap,
syscalls::Sysno,
};

/// Allow the syscall `socket` to open a netlink-socket.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd)]
#[must_use]
pub struct Netlink;

impl RuleSet for Netlink {
fn simple_rules(&self) -> Vec<Sysno> {
Vec::default()
}

#[allow(clippy::as_conversions)]
fn conditional_rules(&self) -> HashMap<Sysno, Vec<Rule>> {
/// `AF_NETLINK` as `u64`.
const AF_NETLINK: u64 = libc::AF_NETLINK as u64;

/// `SOCK_RAW` as `u64`.
const SOCK_RAW: u64 = libc::SOCK_RAW as u64;

let rule = Rule::new(Sysno::socket)
.and_condition(Filter::new(0, Comparator::MaskedEq(AF_NETLINK), AF_NETLINK))
.and_condition(Filter::new(1, Comparator::MaskedEq(SOCK_RAW), SOCK_RAW));
HashMap::from([(Sysno::socket, Vec::from([rule]))])
}

fn name(&self) -> &'static str {
"Netlink"
}
}

#[cfg(test)]
mod tests {
use {super::Netlink, crate::RuleSet as _, syscalls::Sysno};

#[test]
fn name() {
assert_eq!(Netlink.name(), "Netlink");
}

#[test]
fn simple_rules() {
let rules = Netlink.simple_rules();
assert!(rules.is_empty());
}

#[test]
fn conditional_rules() {
let rules = Netlink.conditional_rules();
assert_eq!(rules.len(), 1);
assert!(rules.contains_key(&Sysno::socket));
}
}
40 changes: 40 additions & 0 deletions src/builtins/network/socket_pair.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Allow `sys_socketpair`.

use {crate::RuleSet, syscalls::Sysno};

/// Allow the syscall `socketpair` to create a pair of connected sockets.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd)]
#[must_use]
pub struct SocketPair;

impl RuleSet for SocketPair {
fn simple_rules(&self) -> Vec<Sysno> {
Vec::from([Sysno::socketpair])
}

fn name(&self) -> &'static str {
"SocketPair"
}
}

#[cfg(test)]
mod tests {
use {super::SocketPair, crate::RuleSet as _, syscalls::Sysno};

#[test]
fn name() {
assert_eq!(SocketPair.name(), "SocketPair");
}

#[test]
fn simple_rules() {
let rules = SocketPair.simple_rules();
assert_eq!(rules.len(), 1);
assert!(rules.contains(&Sysno::socketpair));
}

#[test]
fn conditional_rules() {
assert!(SocketPair.conditional_rules().is_empty());
}
}
Loading