From f1db25e7ad7f1daa75aefff7944639981f88af98 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 3 Jul 2024 16:55:57 +0200 Subject: [PATCH 1/7] feat(builtins): add builtin `Netlink` --- src/builtins/mod.rs | 30 ++++------ src/builtins/{network.rs => network/mod.rs} | 14 +++-- src/builtins/network/netlink.rs | 62 +++++++++++++++++++++ 3 files changed, 83 insertions(+), 23 deletions(-) rename src/builtins/{network.rs => network/mod.rs} (98%) create mode 100644 src/builtins/network/netlink.rs diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 43b3c50..bbdc1bd 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,5 +1,16 @@ //! Built-in [`RuleSet`](crate::RuleSet)s +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, Netlink}, 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 +27,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; diff --git a/src/builtins/network.rs b/src/builtins/network/mod.rs similarity index 98% rename from src/builtins/network.rs rename to src/builtins/network/mod.rs index c959c1d..e583806 100644 --- a/src/builtins/network.rs +++ b/src/builtins/network/mod.rs @@ -1,11 +1,15 @@ //! Contains a [`RuleSet`] for allowing networking-related syscalls. -use std::collections::{HashMap, HashSet}; +pub mod netlink; -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; // TODO: make bind calls conditional on the DGRAM/UNIX/STREAM flag in each function @@ -205,7 +209,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); diff --git a/src/builtins/network/netlink.rs b/src/builtins/network/netlink.rs new file mode 100644 index 0000000..62befbd --- /dev/null +++ b/src/builtins/network/netlink.rs @@ -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 { + Vec::default() + } + + #[allow(clippy::as_conversions)] + fn conditional_rules(&self) -> HashMap> { + /// `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)); + } +} From 03c68ce35806a19b38f574a92896c38d5228036b Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 3 Jul 2024 17:00:46 +0200 Subject: [PATCH 2/7] feat(builtins): add builtin `SocketPair` --- src/builtins/mod.rs | 5 +++- src/builtins/network/mod.rs | 3 ++- src/builtins/network/socket_pair.rs | 40 +++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/builtins/network/socket_pair.rs diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index bbdc1bd..81b0e5c 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -8,7 +8,10 @@ pub mod systemio; pub mod time; pub use self::{ - basic::BasicCapabilities, network::{Networking, Netlink}, systemio::SystemIO, time::Time, + basic::BasicCapabilities, + network::{Networking, Netlink, SocketPair}, + systemio::SystemIO, + time::Time, }; /// A struct whose purpose is to make you read the documentation for the function you're calling. diff --git a/src/builtins/network/mod.rs b/src/builtins/network/mod.rs index e583806..7aa3038 100644 --- a/src/builtins/network/mod.rs +++ b/src/builtins/network/mod.rs @@ -1,6 +1,7 @@ //! Contains a [`RuleSet`] for allowing networking-related syscalls. pub mod netlink; +pub mod socket_pair; use { super::YesReally, @@ -9,7 +10,7 @@ use { syscalls::Sysno, }; -pub use self::netlink::Netlink; +pub use self::{netlink::Netlink, socket_pair::SocketPair}; // TODO: make bind calls conditional on the DGRAM/UNIX/STREAM flag in each function diff --git a/src/builtins/network/socket_pair.rs b/src/builtins/network/socket_pair.rs new file mode 100644 index 0000000..1a9c181 --- /dev/null +++ b/src/builtins/network/socket_pair.rs @@ -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 { + 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()); + } +} From 56db3dd055e704fd6f19dcc131ab5a2e0dc32c45 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 3 Jul 2024 17:02:48 +0200 Subject: [PATCH 3/7] feat(builtins): add builtin `Kill` --- src/builtins/kill.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/builtins/mod.rs | 31 +++++++++++++------------------ 2 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 src/builtins/kill.rs diff --git a/src/builtins/kill.rs b/src/builtins/kill.rs new file mode 100644 index 0000000..4b21841 --- /dev/null +++ b/src/builtins/kill.rs @@ -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 { + 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()); + } +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 43b3c50..16ef55e 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,5 +1,17 @@ //! Built-in [`RuleSet`](crate::RuleSet)s +pub mod basic; +pub mod danger_zone; +pub mod kill; +pub mod network; +pub mod pipes; +pub mod systemio; +pub mod time; + +pub use self::{ + basic::BasicCapabilities, kill::Kill, 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 f50fcc54c43c12d089319f4c897ca0868ccc076e Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 3 Jul 2024 17:04:16 +0200 Subject: [PATCH 4/7] feat(builtins): add builtin `Truncate` --- src/builtins/mod.rs | 32 ++++++++++++++----------------- src/builtins/truncate.rs | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 src/builtins/truncate.rs diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 43b3c50..916a457 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,5 +1,18 @@ //! Built-in [`RuleSet`](crate::RuleSet)s +pub mod basic; +pub mod danger_zone; +pub mod network; +pub mod pipes; +pub mod systemio; +pub mod time; +pub mod truncate; + +pub use self::{ + basic::BasicCapabilities, network::Networking, systemio::SystemIO, time::Time, + truncate::Truncate, +}; + /// 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 +29,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; diff --git a/src/builtins/truncate.rs b/src/builtins/truncate.rs new file mode 100644 index 0000000..626774c --- /dev/null +++ b/src/builtins/truncate.rs @@ -0,0 +1,41 @@ +//! Allow `sys_truncate` and `sys_ftruncate`. + +use {crate::RuleSet, syscalls::Sysno}; + +/// Allow the syscalls `truncate` and `ftruncate` to truncate files. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd)] +#[must_use] +pub struct Truncate; + +impl RuleSet for Truncate { + fn simple_rules(&self) -> Vec { + Vec::from([Sysno::truncate, Sysno::ftruncate]) + } + + fn name(&self) -> &'static str { + "Truncate" + } +} + +#[cfg(test)] +mod tests { + use {super::Truncate, crate::RuleSet as _, syscalls::Sysno}; + + #[test] + fn name() { + assert_eq!(Truncate.name(), "Truncate"); + } + + #[test] + fn simple_rules() { + let rules = Truncate.simple_rules(); + assert_eq!(rules.len(), 2); + assert!(rules.contains(&Sysno::truncate)); + assert!(rules.contains(&Sysno::ftruncate)); + } + + #[test] + fn conditional_rules() { + assert!(Truncate.conditional_rules().is_empty()); + } +} From 4a5c30a835a536969d3c139c63285ba7f3ef7ce9 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 9 Oct 2024 15:52:50 +0200 Subject: [PATCH 5/7] 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 139264a4c65ebd5583fdabaa78f163a5047054ba Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 9 Oct 2024 15:55:51 +0200 Subject: [PATCH 6/7] feat(builtins): add builtin `UserId` --- src/builtins/mod.rs | 5 +- src/builtins/user_id.rs | 135 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/builtins/user_id.rs diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index e767dce..4e3b462 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -9,8 +9,11 @@ pub mod network; pub mod pipes; pub mod systemio; pub mod time; +pub mod user_id; -pub use self::{basic::BasicCapabilities, network::Networking, systemio::SystemIO, time::Time}; +pub use self::{ + basic::BasicCapabilities, network::Networking, systemio::SystemIO, time::Time, 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 diff --git a/src/builtins/user_id.rs b/src/builtins/user_id.rs new file mode 100644 index 0000000..3c95449 --- /dev/null +++ b/src/builtins/user_id.rs @@ -0,0 +1,135 @@ +//! Allow various user ID related syscalls. + +use {super::YesReally, crate::RuleSet, std::collections::BTreeSet, syscalls::Sysno}; + +/// Allow querying and modifying user IDs. +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[must_use] +pub struct UserId { + /// A set of permitted syscalls, added by various constructors and methods. + syscalls: BTreeSet, +} + +impl UserId { + /// Construct a new rule, which allows everything: + /// Querying and modifying user IDs without restriction. + pub fn everything() -> YesReally { + Self::default().allow_everything() + } + + /// Construct a new rule, which allows querying user IDs without restriction. + pub fn get() -> Self { + Self::default().allow_get() + } + + /// Construct a new rule, which allows nothing. + pub fn nothing() -> Self { + Self::default() + } + + /// Construct a new rule, which allows modifying user IDs without restriction. + pub fn set() -> YesReally { + Self::default().allow_set() + } + + allow! { + /// Allow modifying and querying user IDs without restriction. + pub unsafe fn allow_everything() { + /// Allow modifying user IDs without restriction. + pub unsafe fn allow_set() { + /// Allow modifying the user ID used for filesystem checks. + pub unsafe fn allow_setfsuid(setfsuid); + + /// Allow modifying the real, effective and saved user ID. + pub unsafe fn allow_setresuid(setresuid); + + /// Allow modifying the real and/or effective user ID. + pub unsafe fn allow_setreuid(setreuid); + + /// Allow modifying the real user ID. + pub unsafe fn allow_setuid(setuid); + } + + /// Allow querying user IDs without restriction. + pub fn allow_get() { + /// Allow querying the effective user ID. + pub fn allow_geteuid(geteuid); + + /// Allow querying the real, effective and saved user ID. + pub fn allow_getresuid(getresuid); + + /// Allow querying the real user ID. + pub fn allow_getuid(getuid); + } + } + } +} + +impl RuleSet for UserId { + fn simple_rules(&self) -> Vec { + self.syscalls.iter().cloned().collect() + } + + fn name(&self) -> &'static str { + "UserId" + } +} + +#[cfg(test)] +mod tests { + use {super::UserId, crate::RuleSet as _, syscalls::Sysno}; + + #[test] + fn everything() { + let rules = UserId::everything().yes_really(); + assert_eq!(rules.name(), "UserId"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 7); + assert!(simple_rules.contains(&Sysno::geteuid)); + assert!(simple_rules.contains(&Sysno::getresuid)); + assert!(simple_rules.contains(&Sysno::getuid)); + assert!(simple_rules.contains(&Sysno::setfsuid)); + assert!(simple_rules.contains(&Sysno::setresuid)); + assert!(simple_rules.contains(&Sysno::setreuid)); + assert!(simple_rules.contains(&Sysno::setuid)); + } + + #[test] + fn get() { + let rules = UserId::get(); + assert_eq!(rules.name(), "UserId"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 3); + assert!(simple_rules.contains(&Sysno::geteuid)); + assert!(simple_rules.contains(&Sysno::getresuid)); + assert!(simple_rules.contains(&Sysno::getuid)); + } + + #[test] + fn set() { + let rules = UserId::set().yes_really(); + assert_eq!(rules.name(), "UserId"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert_eq!(simple_rules.len(), 4); + assert!(simple_rules.contains(&Sysno::setfsuid)); + assert!(simple_rules.contains(&Sysno::setresuid)); + assert!(simple_rules.contains(&Sysno::setreuid)); + assert!(simple_rules.contains(&Sysno::setuid)); + } + + #[test] + fn nothing() { + let rules = UserId::nothing(); + assert_eq!(rules.name(), "UserId"); + assert!(rules.conditional_rules().is_empty()); + + let simple_rules = rules.simple_rules(); + assert!(simple_rules.is_empty()); + } +} From 76d9a3417825f57624b9a6206e01553c403e6691 Mon Sep 17 00:00:00 2001 From: Sebastian Walz Date: Wed, 9 Oct 2024 15:57:48 +0200 Subject: [PATCH 7/7] 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)); + } +}