From 4a5c30a835a536969d3c139c63285ba7f3ef7ce9 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 9 Oct 2024 15:52:50 +0200 Subject: [PATCH 1/2] feat(builtins): introduce `allow!`-macro --- src/builtins/allow.rs | 164 ++++++++++++++++++++++++++++++++++++++++++ src/builtins/mod.rs | 31 ++++---- 2 files changed, 177 insertions(+), 18 deletions(-) create mode 100644 src/builtins/allow.rs diff --git a/src/builtins/allow.rs b/src/builtins/allow.rs new file mode 100644 index 0000000..352a48b --- /dev/null +++ b/src/builtins/allow.rs @@ -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`. + // + // 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 { + 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`. + // + // 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 { + 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 )* } }; +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 43b3c50..e767dce 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,5 +1,17 @@ //! Built-in [`RuleSet`](crate::RuleSet)s +#[macro_use] +pub mod allow; + +pub mod basic; +pub mod danger_zone; +pub mod network; +pub mod pipes; +pub mod systemio; +pub mod time; + +pub use self::{basic::BasicCapabilities, network::Networking, systemio::SystemIO, time::Time}; + /// 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. @@ -16,23 +28,6 @@ impl YesReally { /// Make a [`YesReally`]. pub fn new(inner: T) -> YesReally { - 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; From 76d9a3417825f57624b9a6206e01553c403e6691 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 9 Oct 2024 15:57:48 +0200 Subject: [PATCH 2/2] feat(builtins): extend builtin `Time` --- src/builtins/time.rs | 224 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 200 insertions(+), 24 deletions(-) diff --git a/src/builtins/time.rs b/src/builtins/time.rs index badf0bb..8f2b381 100644 --- a/src/builtins/time.rs +++ b/src/builtins/time.rs @@ -1,45 +1,221 @@ -//! Contains a [`RuleSet`] for allowing time-related syscalls, but check the comments for why you -//! probably don't actually need to enable them. +//! Allow various time related syscalls. -use std::collections::HashSet; - -use syscalls::Sysno; - -use crate::RuleSet; +use {super::YesReally, crate::RuleSet, std::collections::BTreeSet, syscalls::Sysno}; +/// Allow querying and modifying time as well as sleeping. +#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd)] #[must_use] -/// Enable syscalls related to time. pub struct Time { - /// Syscalls that are allowed - allowed: HashSet, + /// A set of permitted syscalls, added by various constructors and methods. + syscalls: BTreeSet, } impl Time { - /// Create a new Time [`RuleSet`] with nothing allowed by default. - pub fn nothing() -> Time { - Time { - allowed: HashSet::new(), - } + /// Construct a new rule, which allows everything: + /// Querying and modifying time as well as sleeping without restriction. + pub fn everything() -> YesReally { + Self::default().allow_everything() + } + + /// Construct a new rule, which allows modifying time without restriction. + pub fn modify() -> YesReally { + Self::default().allow_modify() + } + + /// Construct a new rule, which allows nothing. + pub fn nothing() -> Self { + Self::default() + } + + /// Construct a new rule, which allows querying time without restriction. + pub fn query() -> Self { + Self::default().allow_query() + } + + /// Construct a new rule, which allows querying and modifying time without restriction. + pub fn query_and_modify() -> YesReally { + Self::default().allow_query().allow_modify() + } + + /// Construct a new rule, which allows querying time as well as sleeping without restriction. + pub fn query_and_sleep() -> Self { + Self::default().allow_query_and_sleep() + } + + /// Construct a new rule, which allows sleeping without restriction. + pub fn sleep() -> Self { + Self::default().allow_sleep() } -/// On most 64 bit systems glibc and musl both use the -/// [`vDSO`](https://man7.org/linux/man-pages/man7/vdso.7.html) to compute the time directly with -/// rdtsc rather than calling the `clock_gettime` syscall, so in most cases you don't need to -/// actually enable this. - pub fn allow_gettime(mut self) -> Time { - self.allowed - .extend([Sysno::clock_gettime, Sysno::clock_getres]); + allow! { + /// Allow querying and modifying time as well as sleeping without restriction. + pub unsafe fn allow_everything() { + /// Allow modifying time without restriction. + pub unsafe fn allow_modify() { + /// Allow the `adjtimex` syscall to tune a kernel clock. + pub unsafe fn allow_adjtimex(adjtimex); + + /// Allow the `clock_adjtime` syscall to tune a kernel clock. + pub unsafe fn allow_clock_adjtime(clock_adjtime); + + /// Allow the `clock_settime` syscall to set the time of a clock. + pub unsafe fn allow_clock_settime(clock_settime); + + /// Allow the `settimeofday` syscall to set the time. + pub unsafe fn allow_settimeofday(settimeofday); + } - self + /// Allow querying time and sleeping without restriction. + pub fn allow_query_and_sleep() { + /// Allow querying time without restriction. + pub fn allow_query() { + /// On most 64 bit systems glibc and musl both use the + /// [`vDSO`](https://man7.org/linux/man-pages/man7/vdso.7.html) to compute the time directly with + /// rdtsc rather than calling the `clock_gettime` syscall, so in most cases you don't need to + /// actually enable this. + pub fn allow_gettime() { + /// Allow the `clock_getres` syscall to get the clock resolution. + pub fn allow_clock_getres(clock_getres); + + /// Allow the `clock_gettime` syscall to get the time of a clock. + pub fn allow_clock_gettime(clock_gettime); + } + + /// Allow the `gettimeofday` syscall to get the time. + pub fn allow_gettimeofday(gettimeofday); + + /// Allow the `time` syscall to get the time in seconds. + pub fn allow_time(time); + } + + /// Allow sleeping without restriction. + pub fn allow_sleep() { + /// Allow the `clock_nanosleep` syscall. + pub fn allow_clock_nanosleep(clock_nanosleep); + + /// Allow the `nanosleep` syscall. + pub fn allow_nanosleep(nanosleep); + } + } + } } } impl RuleSet for Time { fn simple_rules(&self) -> Vec { - self.allowed.iter().copied().collect() + self.syscalls.iter().cloned().collect() } fn name(&self) -> &'static str { "Time" } } + +#[cfg(test)] +mod tests { + use {super::Time, crate::RuleSet as _, syscalls::Sysno}; + + #[test] + fn everything() { + let rules = Time::everything().yes_really(); + assert_eq!(rules.name(), "Time"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 10); + assert!(simple_rules.contains(&Sysno::adjtimex)); + assert!(simple_rules.contains(&Sysno::clock_adjtime)); + assert!(simple_rules.contains(&Sysno::clock_getres)); + assert!(simple_rules.contains(&Sysno::clock_gettime)); + assert!(simple_rules.contains(&Sysno::clock_nanosleep)); + assert!(simple_rules.contains(&Sysno::clock_settime)); + assert!(simple_rules.contains(&Sysno::gettimeofday)); + assert!(simple_rules.contains(&Sysno::nanosleep)); + assert!(simple_rules.contains(&Sysno::settimeofday)); + assert!(simple_rules.contains(&Sysno::time)); + } + + #[test] + fn modify() { + let rules = Time::modify().yes_really(); + assert_eq!(rules.name(), "Time"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 4); + assert!(simple_rules.contains(&Sysno::adjtimex)); + assert!(simple_rules.contains(&Sysno::clock_adjtime)); + assert!(simple_rules.contains(&Sysno::clock_settime)); + assert!(simple_rules.contains(&Sysno::settimeofday)); + } + + #[test] + fn nothing() { + let rules = Time::nothing(); + assert_eq!(rules.name(), "Time"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert!(simple_rules.is_empty()); + } + + #[test] + fn query() { + let rules = Time::query(); + assert_eq!(rules.name(), "Time"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 4); + assert!(simple_rules.contains(&Sysno::clock_getres)); + assert!(simple_rules.contains(&Sysno::clock_gettime)); + assert!(simple_rules.contains(&Sysno::gettimeofday)); + assert!(simple_rules.contains(&Sysno::time)); + } + + #[test] + fn query_and_modify() { + let rules = Time::query_and_modify().yes_really(); + assert_eq!(rules.name(), "Time"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 8); + assert!(simple_rules.contains(&Sysno::adjtimex)); + assert!(simple_rules.contains(&Sysno::clock_adjtime)); + assert!(simple_rules.contains(&Sysno::clock_getres)); + assert!(simple_rules.contains(&Sysno::clock_gettime)); + assert!(simple_rules.contains(&Sysno::clock_settime)); + assert!(simple_rules.contains(&Sysno::gettimeofday)); + assert!(simple_rules.contains(&Sysno::settimeofday)); + assert!(simple_rules.contains(&Sysno::time)); + } + + #[test] + fn query_and_sleep() { + let rules = Time::query_and_sleep(); + assert_eq!(rules.name(), "Time"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 6); + assert!(simple_rules.contains(&Sysno::clock_getres)); + assert!(simple_rules.contains(&Sysno::clock_gettime)); + assert!(simple_rules.contains(&Sysno::clock_nanosleep)); + assert!(simple_rules.contains(&Sysno::gettimeofday)); + assert!(simple_rules.contains(&Sysno::nanosleep)); + assert!(simple_rules.contains(&Sysno::time)); + } + + #[test] + fn sleep() { + let rules = Time::sleep(); + assert_eq!(rules.name(), "Time"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 2); + assert!(simple_rules.contains(&Sysno::clock_nanosleep)); + assert!(simple_rules.contains(&Sysno::nanosleep)); + } +}