Code Monkey home page Code Monkey logo

libseccomp-rs's Introduction

libseccomp-rs

build-test Latest release on crates.io Documentation on docs.rs codecov MSRV: 1.63

Rust Language Bindings for the libseccomp Library

The libseccomp library provides an easy to use, platform independent, interface to the Linux Kernel's syscall filtering mechanism. The libseccomp API is designed to abstract away the underlying BPF based syscall filter language and present a more conventional function-call based filtering interface that should be familiar to, and easily adopted by, application developers.

The libseccomp-rs provides a Rust based interface to the libseccomp library. This repository contains libseccomp and libseccomp-sys crates that enable developers to use the libseccomp API in Rust.

  • libseccomp: High-level safe API
  • libseccomp-sys: Low-level unsafe API

CHANGELOG

Example

Create and load a single seccomp rule:

use libseccomp::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Creates and returns a new filter context.
    let mut filter = ScmpFilterContext::new(ScmpAction::Allow)?;

    // Adds an architecture to the filter.
    filter.add_arch(ScmpArch::X8664)?;

    // Returns the number of a syscall by name.
    let syscall = ScmpSyscall::from_name("dup3")?;

    // Adds a single rule for an unconditional action on the syscall.
    filter.add_rule(ScmpAction::Errno(10), syscall)?;

    // Loads the filter context into the kernel.
    filter.load()?;

    // The syscall fails by the seccomp rule.
    assert_eq!(unsafe { libc::dup3(0, 100, libc::O_CLOEXEC) }, -libc::EPERM);
    // The errno is the number specified by the seccomp action.
    assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), 10);

    Ok(())
}

Requirements

Before using the libseccomp crate, you need to install the libseccomp library for your system. The libseccomp library version 2.5.0 or newer is required.

Installing the libseccomp library from a package

e.g. Debian-based Linux

$ sudo apt install libseccomp-dev

Building and installing the libseccomp library from sources

If you want to build the libseccomp library from an official release tarball instead of the package, you should follow the quick step.

$ LIBSECCOMP_VERSION=2.5.3
$ wget https://github.com/seccomp/libseccomp/releases/download/v${LIBSECCOMP_VERSION}/libseccomp-${LIBSECCOMP_VERSION}.tar.gz
$ tar xvf libseccomp-${LIBSECCOMP_VERSION}.tar.gz
$ cd libseccomp-${LIBSECCOMP_VERSION}
$ ./configure
$ make
$ sudo make install

For more details, see the libseccomp library repository.

Setup

If you use the libseccomp crate with dynamically linked the libseccomp library, you do not need additional settings.

However, if you want to use the libseccomp crate against musl-libc with statically linked the libseccomp library, you have to set the LIBSECCOMP_LINK_TYPE and LIBSECCOMP_LIB_PATH environment variables as follows.

$ export LIBSECCOMP_LINK_TYPE=static
$ export LIBSECCOMP_LIB_PATH="the path of the directory containing libseccomp.a (e.g. /usr/lib)"

Note: To build the libseccomp crate against musl-libc, you need to build the libseccomp library manually for musl-libc or use a musl-based distribution that provides a package for the statically-linked libseccomp library

Now, add the following to your Cargo.toml to start building the libseccomp crate.

[dependencies]
libseccomp = "0.3.0"

Minimum Supported Rust Version (MSRV) policy

The 0.3.x line has 1.46 as MSRV.

Starting with 0.4.0, our MSRV will be increased when necessary or appropriate. However, it will never be increased to a version greater than the last stable rust version minus two.

A MSRV change is not considered a breaking change.

Testing the crate

The libseccomp crate provides a number of unit tests. If you want to run the standard regression tests, you can execute the following command.

$ make test

How to contribute

Anyone is welcome to join and contribute code, documentation, and use cases.

For details on how to contribute to the libseccomp-rs project, please see the contributing document.

License

This crate is licensed under:

  • MIT License (see LICENSE-MIT); or
  • Apache 2.0 License (see LICENSE-APACHE),

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in libseccomp-rs by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

libseccomp-rs's People

Contributors

anguslees avatar boustrophedon avatar dependabot[bot] avatar manasugi avatar mayamrinal avatar rusty-snake avatar savithry-j avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

libseccomp-rs's Issues

Reconsider SeccompErrno design for `ScmpFilterAttr::ApiSysRawRc`

Is your feature request related to a problem? Please describe.

With ScompFilterAttr::ApiSysRawRc enabled, some libseccomp apis will return a system's errno instead of ECANCELED.
But, current implementations cannot handle the system's errno other than SeccompErrono, so users have to extract the errno by themselves using std::io::Error::last_os_error(). This is a little bit tiring.

Describe the solution you'd like

There are some solutions to tackle the problem.

  1. Add SYSRAW(i32) to SeccompError:
    Example:
pub enum SeccompErrno {
    ...
    SYSRAW(i32),
}
  1. Add SYSRAW(i32) to ErrorKind:
    Example:
pub(crate) enum ErrorKind {
    ...
    SYSRAW(i32),
}
  1. Add all errno to SeccompErrno (#205 approach):
    Example:
pub enum SeccompErrno {
    ...
   EBUSY,
   EBADFD
}

Ref. https://docs.rs/nix/latest/src/nix/errno.rs.html#1125-1257

  1. Use nix::Errno instead of SeccompErrno (stop maintaining SeccompErrno).
    Example:
pub(crate) enum ErrorKind {
    /// An error that represents error code on failure of the libseccomp API.
    Errno(nix:Errno),
    /// A parse error occurred while trying to convert a value.
    ParseError,
    ...
}

Rework MaskedEqual compare

The arguments of ScmpArgCompare::new are a bit confusing ATM. In the most cases the third argument is the value with which the argument is compared and the fourth argument is None. If you use Some(_) for the fourth argument, it is no effect. The fourth argument is therefore effectively ignore. But if you use masked equality, the third argument is the mask and the fourth argument is the value used for comparison. And if you specify it as None you implicitly specified 0. Not really a nice way for a high-level wrapper.

The first Idea would be to make the third argument always be the value and the fourth argument to be the mask (still and Option<_>). However this silently changes behaviour in a drastically way for people who just upgrade the version in Cargo.toml without reading the CHANGELOG. Moreover it would not solve the ugly None.

You could create a new_masked function. However making one op part of ScmpArgCompare isn't a code API design IMHO.

My current idea is to move the mask into ScmpCompareOp. So

ScmpArgCompare::new(0, ScmpCompareOp::Greater, 2, None);
ScmpArgCompare::new(0, ScmpCompareOp::MaskedEqual, 0b0010, Some(3));

becomes

ScmpArgCompare::new(0, ScmpCompareOp::Greater, 2);
ScmpArgCompare::new(0, ScmpCompareOp::MaskedEqual(0b0010), 3);

WDYT?

Cannot re-export `Error` struct.

Describe the bug
Build failure when use with thiserror

To Reproduce

Code:

use libseccomp::*;

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("{0}")]
    SeccompError(#[from] libseccomp::SeccompError),
}

fn main() -> Result<(), Error> {
    // Creates and returns a new filter context.
    let mut filter = ScmpFilterContext::new_filter(ScmpAction::Allow)?;

    // Adds an architecture to the filter.
    filter.add_arch(ScmpArch::X8664)?;

    // Returns the number of a syscall by name.
    let syscall = ScmpSyscall::from_name("dup3")?;

    // Adds a single rule for an unconditional action on the syscall.
    filter.add_rule(ScmpAction::Errno(10), syscall)?;

    // Loads the filter context into the kernel.
    filter.load()?;

    // The dup3 fails by the seccomp rule.
    assert_eq!(
        unsafe { libc::dup3(0, 100, libc::O_CLOEXEC) } as i32,
        -libc::EPERM
    );
    assert_eq!(std::io::Error::last_os_error().raw_os_error().unwrap(), 10);

    Ok(())
}

Compile with:
cargo build

See error:

error[E0603]: struct `SeccompError` is private
  --> src/main.rs:6:38
   |
6  |     SeccompError(#[from] libseccomp::SeccompError),
   |                                      ^^^^^^^^^^^^ private struct
   |
note: the struct `SeccompError` is defined here
  --> .../libseccomp-0.2.3/src/lib.rs:56:21
   |
56 | use error::{Result, SeccompError};
   |                     ^^^^^^^^^^^^

Expected behavior
Build success.

Environment

  • libseccomp: 0.5.4.1
  • libseccomp-rs: 0.2.3
  • Rust: rustc 1.62.1 (e092d0b6b 2022-07-16)
  • Linux: 5.18.16-arch1-1
  • Architecture: x86_64

Rework Seccomp Notification

Current Seccomp Notification implementation is not good as high-level wrapper.
Hence, we have to rework Seccomp Notification based on libseccomp-golang.
This work changes the API interfaces dramatically, but now is the perfect time to rework this while there are not many users who currently are using Seccomp Notification through libseccomp-rs.

In addtion, I'll add the documentation and unit tests about it,

Add get/set functions for ScmpFilterAttr

Add the following functions like the existing {get,set}_no_new_privs_bit().

  • get_act_default()
  • {get,set}_act_badarch()
  • {get,set}_ctl_nnp()
  • {get,set}_ctl_tsync()
  • {get,set}_ctl_log()
  • {get,set}_ctl_ssb()
  • {get,set}_ctl_optimize()
  • {get,set}_api_sysrawrc()

Prepare for v0.2.1

Release:
libseccomp: v0.2.1

TODO:

  • Merge #75 into main branch
  • Validate the crate documentation
  • Change cargo.toml/README.md of the libseccomp crate for v0.2.1
  • Create v0.2.1 tag
  • Publish the crates on crates.io
    • Run cargo doc --no-deps
  • Create the release page in Github

`SCMP_SYS`

libseccomp provides a SCMP_SYS macro to resolve syscall names to numbers at compile time. This is faster and handier than seccomp_syscall_resolve_name ScmpSyscall::from_name.

Definition in seccomp.h:

#define SCMP_SYS(x)     (__SNR_##x)

__SNR_* is defined in seccomp-syscalls.h to either __NR_* if it is defined or __PNR_* (a negative pseudo number).

It would be nice if this static resolving of syscall names would be available in Rust to.

Other helpful resource:

`seccomp_export_bpf_mem`

Upstream commit: seccomp/libseccomp@3f0e47f (not release yet)

libseccomp-sys:

extern "C" {
    /// Generate seccomp Berkeley Packet Filter (BPF) code and export it to a buffer
    ///
    /// - `ctx`: the filter context
    /// - `buf`: the destination buffer
    /// - `len`: on input the length of the buffer, on output the number of bytes in the program
    ///
    /// This function generates seccomp Berkeley Packer Filter (BPF) code and writes
    /// it to the given buffer.  Returns zero on success, negative values on failure.
    pub fn seccomp_export_bpf_mem(ctx: const_scmp_filter_ctx, buf: *mut /* FIXME */, len: *mut c_size_t) -> c_int;
}

libseccomp:

impl ScmpFilterContext {
    pub fn export_bpf_mem(&self) -> Vec<u8> {
        ...
    }
}

Prepare for v0.2.0

v0.2.0 milestone: https://github.com/libseccomp-rs/libseccomp-rs/milestone/1

Release:
libseccomp: v0.2.0
libseccomp-sys: v0.2.0

TODO:

  • Merge all PRs in the milestone into main branch
  • Update CHANGELOG.md
    • Add all changes from v0.1.3
    • The interfaces will be changed dramatically from v0.2.0, so we MUST write the annotations of the change.
  • Validate the crate documentation
  • Change cargo.toml/README.md of libseccomp/libseccomp-sys for v0.2.0
  • Create v0.2.0 tag
  • Publish the crates on crates.io
    • Run cargo doc --no-deps
    • At first, I need to pubslih the libseccomp-sys crate. Second, I'll publish the libseccomp crate.
  • Create the release page in Github

CI Warnings and use of actions-rs actions

There are CI warnings because

All come from action-rs/toolchain and actions-rs/tarpaulin. For action-rs/toolchain they are tracked in actions-rs/toolchain#221 and actions-rs/toolchain#219. While actions-rs/tarpaulin does not have an issue for them yet.

Since both actions do not seem to see that much maintenance (last release ~1,5 years ago, Open PRs w/o any comments for over 1 year, Unmerged dependabot PRs with security updates, ...) I would put up for discussion whether we should not better get rid of them to avoid future problems.

io_safety

  • ScmpFd
  • ScmpFilterContext::export_{bpc,pfc}

SeccompError: Expose libseccomp errno

It looks like there are cases where programs need to know the errno returned by libseccomp.
Example at https://github.com/flatpak/flatpak/blob/8ad534f65c48ff0a91f6f4472924b152752bbf74/common/flatpak-run.c#L3292.

We should add an errno(&self) -> Option<Errno> function to SeccompError.

Tasks (draft):

  • Add errno field to SeccompError
  • Add errno getter function to `SeccompError
  • Add SeccompErrno type (enum)
  • Make sure errno is saved in all relevant places
  • Remove ErrorKind::Errno
  • Use SeccompErrno for fmt:Display.

Move unit tests to tests directory on top

Currently, all unit tests for the libsecomp crate is under the same file as the to-be-tested code.
I think it's right to put unit tests with the production code as Rust book mentioned here, but we'll lose an overview and a clear separation between production and test code.
Also, the files'll grow huge while containing both.

Hence, I'd like to move unit tests to a tests directory on top for making the code clean.
Originally, the tests directory is for integration tests, but it's good for the future work.

โ”œโ”€โ”€ libseccomp-rs
โ”‚ย ย  โ””โ”€โ”€ libseccomp
โ”‚ย ย  โ””โ”€โ”€ libseccomp-sys
โ”‚ย ย  โ””โ”€โ”€ tests
โ”‚ย ย       โ””โ”€โ”€ xxx.rs

Or, we'll need to split the current huge lib.rs as module like notify.rs

`seccomp_reset(NULL)`

If seccomp_reset() is called with a NULL filter, it resets the library's global task state, including any notification file descriptors retrieved by seccomp_notify_fd(3). Normally this is not needed, but it may be required to continue using the library after a fork() or clone() call to ensure the API level and user notification state is properly reset.

Update ScmpAction::Errno again

#13 has changed ScmpAction::Errno(u32) to ScmpAction::Errno(i32) to make ScmpAction::Errno(libc::EPERM) as easy as possible.

However since you can only use u16, it would be good if it would be ScmpAction::Errno(u16).
Now my idea is to change it to ScmpAction::Errno(u16) and provide ScmpAction::errno(errno: i32) -> Self (this will also simplify from_str's val).

Rework argument comparison

How does argument comparison work in libseccomp?

You can either act on a syscall unconditional (no argument comparison) or conditional (i.e. based on the argument of the syscall. And no you can not dereference pointers.).

If you use argument comparison, you specify rules which compare the nth-value of a syscall to a datum value using a scmp_compare operator.

These rules are logically AND-ed:

If syscall argument comparisons are included in the filter rule, all of the comparisons must be true for the rule to match.

But even more important, you can only have one comparisons for an argument:

When adding syscall argument comparisons to the filter it is important to remember that while it is possible to have multiple comparisons in a single rule, you can only compare each argument once in a single rule. In other words, you can not have multiple comparisons of the 3rd syscall argument in a single rule.

If you try to add multiple comparisons for one argument, add_rule will return Err(_). That's perfectly fine. However from an user PoV it woulb be great if the API would not all this.

Ideas

  1. Adopt libseccomp-golang like add_rule* methods:
    fn add_rule(&mut self, syscall: i32, action ScmpAction) -> Result<()>;
    fn add_rule_conditional(&mut self, syscall: i32, action ScmpAction, comparators: &[ScmpArgCompare]) -> Result<()>;
    fn add_rule_conditional_exact(&mut self, syscall: i32, action ScmpAction, comparators: &[ScmpArgCompare]) -> Result<()>;
    fn add_rule_exact(&mut self, syscall: i32, action ScmpAction) -> Result<()>;
  2. Add an own ArgumentComparators type:
    // Same ABI: `&[libseccomp-sys::scmp_arg_cmp] as *const libseccomp-sys::scmp_arg_cmp` <=> `const struct scmp_arg_cmp *`
    struct ArgComp([libseccomp-sys::scmp_arg_cmp; 6]);
    impl ArgComp {
        pub fn get(n: usize) -> ScmpArgCompare;
        pub fn set(n: usize, op: ScmpCompareOp, datum: u64);
        pub fn set_noreplace(n: usize, op: ScmpCompareOp, datum: u64);
        pub fn from_iter(i: impl IntoIterator<Item = (ScmpCompareOp, u64)>) -> Result<Self>`
        pub fn from_iter(i: impl IntoIterator<Item = ScmpArgCompare) -> Result<Self>`
    }
    As you see this is an unfinish idea.
  3. (Ab)use rust's operators:
    ctx.add_rule(ScmpAction::Allow, 1000, &[ARG0 == 5, ARG1 != 2, ARG2 >= 64, ARG3 & 0x0f0 == 1234])
    This is rust, not python so we need cmp![ARG0 == โ€ฆ.

Basically I want to simplify

ctx.add_rule(ScmpAction::Allow, 1000, Some(&[ScmpArgCompare::new(0, ScmpCompareOp::Equal, 1), ScmpArgCompare::new(2, ScmpCompareOp::NotEqual, 32)]))?;

to something like

ctx.add_rule(ScmpAction::Allow, 1000, ArgCmp(arg_0_eq(1), None, arg_2_ne(32)))?;

Obvious this needs some time, thinking, input, ...

`ScmpSyscall`

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct ScmpSyscall(i32);
impl ScmpSyscall {
    fn as_sys(self) -> i32 {
        ...
    }

    fn from_sys(nr: i32) -> Self {
        ...
    }

    pub fn from_name(name: &str) -> Result<Self> {
        ...
    }

    pub fn from_name_by_arch(name: &str, arch: ScmpArch) -> Result<Self> {
        ...
    }

    pub fn get_name(&self) -> Result<String> {
        ...
    }

    pub fn get_name_by_arch(&self, arch: ScmpArch) -> Result<String> {
        ...
    }
}
#[deprecated(since = "0.y.z", note = "Use ScmpSyscall::from_name/ScmpSyscall::from_name_by_arch instead.")]
fn get_syscall_from_name() {}
#[deprecated(since = "0.y.z", note = "Use ScmpSyscall::get_name_by_arch instead.")]
fn get_syscall_name_from_arch() {}

Note: This can be done after 0.2.0 without breaking existing code using an AsSyscall trait.

pub trait AsSyscall: private::Sealed {
    fn as_syscall_nr(&self) -> i32;
}

impl AsSyscall for ScmpSyscall {
    ...
}

impl AsSyscall for i32 {
    ...
}

mod private {
    pub trait Sealed {}

    impl Sealed for ScmpSyscall {}
    impl Sealed for i32 {}
}

impl ScmpFilterContext {
    pub fn add_rule<S: AsSyscall>(
        &mut self,
        action: ScmpAction,
        syscall: S,
        comparators: Option<&[ScmpArgCompare]>,
    ) -> Result<()> {}
}

`seccomp_syscall_resolve_name_rewrite`

with seccomp_syscall_resolve_name_rewrite() rewriting the syscall number for architectures that modify the syscall. Syscall rewriting typically happens in case of a multiplexed syscall, like socketcall(2) or ipc(2) on x86.

Together with #54

Re-export notify module

It would be better to re-export notify module so that users can use the more convenient structure as follows.

Before:

// Example
use libseccomp::*;

notify::notify_id_valid();

After:

// Example
use libseccomp::*;

notify_id_valid();

Currently, I plan to re-export the notify module as private module as follows in order to keep the documentation clean.
However, if I do, this will be a breaking change.

#[cfg(any(libseccomp_v2_5, doc))] 
mod notify;

If I re-export the notify module as public, this won't be a breaking change, but the re-exports section is added to the documentation as follows and it may be huge as new notify features are added. (I'd like to avoid writing pub use notify::*)
re-export-notify-doc

@rusty-snake Which do you prefer? (or Could you tell me the better solution if you have?)

Add a `ScmpNotifCookie` type

Is your feature request related to a problem? Please describe.

N/A

Describe the solution you'd like

struct ScmpNotifCookie(u64) or type ScmpNotifCookie = u64.
The latter wouldn't be a breaking change while the former would allow fn ScmpNotifCookie::is_valid(fd: ScmpFd) -> bool.

Describe alternatives you've considered

N/A

Additional context

N/A

Builder like interface for ScmpFilterContext

Discussed in #154

Originally posted by rusty-snake May 7, 2022
What do you think about extending ScmpFilterContext with an Builder like interface?

ScmpFilterContext::new_filter(ScmpAction::Allow)?
    .add_arch(ScmpArch::X8664)?;
    .add_rule(
        ScmpAction::Errno(libc::EPERM),
        ScmpSyscall::from_name("chroot")?,
    )?
    .add_rule(...)?
    .add_rule(...)?
    .add_rule_conditional(...)?
    .set_ctl_log(true)?
    .load()?;

MSRV

IMHO, we should establish an MSRV.

There are two common MSRV concepts:

  1. latest stable - offset (offset is usually 2).
    Example: The current stable rust version is 1.59, so we would CI check and guarantee that libseccomp-rs works with rust >= (1.59 - 2 = 1.57).
  2. A fixed release (e.g. 1.56) which is bumped from time to time (with a policy like never closes to the latest release than 2 release).

Enable some allowed-by-default lints

Discussed in #146

Originally posted by rusty-snake April 29, 2022
Doc: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html and https://rust-lang.github.io/rust-clippy/stable/index.html

warn(rust_2018_idioms)

contains:

bare-trait-objects, unused-extern-crates, ellipsis-inclusive-range-patterns, elided-lifetimes-in-paths, explicit-outlives-requirements

Reason: Write more idiomatic Rust.

deny(missing_debug_implementations)

Reason: enforce https://rust-lang.github.io/api-guidelines/debuggability.html

deny(missing_docs)

Reason: Every public item should have documentation.

deny(unsafe_op_in_unsafe_fn)

Reason: Safety: Limit unsafe LoC.

Currently, an unsafe fn allows any unsafe operation within its body. However, this can increase the surface area of code that needs to be scrutinized for proper behavior. The unsafe block provides a convenient way to make it clear exactly which parts of the code are performing unsafe operations. In the future, it is desired to change it so that unsafe operations cannot be performed in an unsafe fn without an unsafe block.

warn(clippy::inefficient_to_string)

Reason: performance.

warn(clippy::string_to_string) warn(clippy::semicolon_if_nothing_returned) warn(clippy::clone_on_ref_ptr)

Reason: readability.


Only warn(rust_2018_idioms) and deny(unsafe_op_in_unsafe_fn) would actually require code changes.


Only warn(rust_2018_idioms) and deny(unsafe_op_in_unsafe_fn) would actually require code changes.

Actually deny(missing_docs) too.

More `ScmpNotifResp` constructors

Is your feature request related to a problem? Please describe.

ScmpNotifResp has (logical) invariants you need to manage by hand.

If you specify SECCOMP_USER_NOTIF_FLAG_CONTINUE, error and val must be 0.
If error != 0, val is ignored.

Describe the solution you'd like

Functions to construct a ScmpNotifResp for all responds kind (spoofed success, spoofed errorm continue):

impl ScmpNotifResp {
    fn new_val(id, val: i64) -> Self;
    fn new_error(id, error: i32) -> Self;
    fn new_continue(id) -> Self;
}

Describe alternatives you've considered

N/A

Additional context

N/A

EBUSY not in SeccompErrno

SeccompErrno contains all errnos returned by the libseccomp api. However if you set ApiSysRawRc the errno from the kernel seccomp api is returned instead. This raises to problems

  1. The descriptions of the errno are not (always) correct.
  2. The kernel seccomp api can return errnos which are not returned by the libseccomp api.

1. Can be ignored IMHO because if you set ApiSysRawRc you are interested in the errno and perform your own handling of it instead of logging it (and die). Also it's limited what we can do. However 2. is problematic if you want to match on an errno which is not covered by SeccompErrno.
Looking at the manpage of seccomp(2) list only EBUSY as not covered errno.

Upcomming libseccomp changes

libseccomp next major (2.6)

libseccomp next minor (2.5.5)

libseccomp v2.5.4

  • New syscalls:
    • Linux 5.17: futex_waitv, set_mempolicy_home_node (#175)

Rust 2021 edition

The the time we require rust >1.56 we can switch to the 2021 edition.

io_safety (#166) will requires rust 1.63.

Wrong error description: Setting the attribute with the given value is not allowed

A snippet like

let mut ctx = ScmpFilterContext::new_filter(ScmpAction::Allow)?;
ctx.add_rule(
    ScmpAction::Allow,
    ScmpSyscall::from_name("openat")?,
)?;

will show

Error: Error { kind: Errno(-13), source: None, message: "Setting the attribute with the given value is not allowed" }

because of

const EACCES: &str = "Setting the attribute with the given value is not allowed";


seccomp_rule_add(3):

-EACCCES
The rule conflicts with the filter (for example, the rule action equals the default action of the filter).

Update MAINTAINERS

I'd like to add @rusty-snake as a maintainer who is one of the most active contributors, thanks for your contributions!
Please create a PR to update the MAINTAINERS file :)
Format: name <e-mail address> (@GitHub ID)

clean code: `ensure_zero!`

Currently this is a very common pattern in libseccomp-rs:

let ret = unsafe { seccomp_foo() };
if ret != 0 {
    return Err(SeccompError::new(Errno(ret)));
}
// use ret

I want this to refactor to somewhat like

let foo = unsafe { ensure_zero!(seccomp_foo()) };
// use foo (which has is better name than ret)

Prepare for v.0.2.2

RELEASE:
libseccomp: v0.2.2

TODO:

  • Merge Make the notify module visible in the docs #78 into main branch
  • Validate the crate documentation
  • Change Cargo.toml/README.md/CHANGELOG.md for v.0.2.2
  • Create v0.2.2 tag
  • Publish the crates on crates.io
    • Run cargo doc --no-deps
  • Create the release page in Github

Change return type of `notify_id_valid` to `bool`

Currently notify_id_valid returns Result<(), SeccompError> or more precise it returns Ok(()) if the id is valid or Err(SeccompError::new(Errno(ENOENT))) if it is not valid.

This results in code like this:

if notify_id_valid(fd, req.id).is_err() {
    // abort ...
}

IMHO it would be cleaner if the API would look like

if !notify_id_valid(fd, req.id) {
    // abort ...
}

Documentation

Needs documentation:

Other documentation notes:

  • Highlight ScmpFilterContext somehow to make clear that this is the "main-type" of this crate new users should look at.
  • Use more examples. They will also add better coverage.
  • How do I set ScmpFilterAttr? With set_filter_attr. Add examples and link to it.
  • ScmpFilterAttr::CtlOptimize
    • Which levels are available
  • ScmpCompareOp::MaskedEqual
    • What is masked equality? How does it work?
  • ScmpAction::from_str
    • Which string are supported as input? Add an example.
      • kill? No.
      • SCMP_ACT_KILL? Yes.

Ideas for a higher level notification API

Discussed in #163

Originally posted by rusty-snake June 5, 2022
This are my experiments for a saner unotify API.

The API will be build around the notify-fd. Currently you pass the fd as an integer to the receive/response function. This has little type safety and is a bit odd because you can only have one notify fd.

use std::error::Error as StdError;
use crate::{
    notify_id_valid, Result, ScmpArch, ScmpFd, ScmpNotifReq, ScmpNotifResp, ScmpSyscall,
    NOTIF_FLAG_CONTINUE,
};

//#[derive(Debug)]
pub struct Rotify {
    fd: ScmpFd,
    actions: Vec<RotifAction>, // ignore this filed for now
}

You then use receive/response methods on this type:

impl Rotify {
    pub fn receive(&self) -> Result<RotifReq> {
        let req = ScmpNotifReq::receive(self.fd)?;
        Ok(RotifReq {
            id: RotifCookie(req.id, self.fd),
            pid: RotifPid(req.pid),
            data: RotifData {
                syscall: req.data.syscall,
                arch: req.data.arch,
                instr_pointer: req.data.instr_pointer,
                args: req.data.args,
            },
        })
    }

    // or send_response()?
    pub fn respond(&self, id: RotifCookie, resp: RotifResp) -> Result<()> {
        match resp {
            RotifResp::Continue => ScmpNotifResp::new(id.0, 0, 0, NOTIF_FLAG_CONTINUE),
            RotifResp::Val(val) => ScmpNotifResp::new(id.0, val as i64, 0, 0),
            RotifResp::Error(error) => ScmpNotifResp::new(id.0, 0, -(error as i32), 0),
        }
        .respond(self.fd)
    }
}

The response type is an enum instead of an struct with logical invariants:

#[derive(Debug)]
#[non_exhaustive]
pub enum RotifResp {
    Continue,
    Val(i64),   // Are negative values supported? If not change this to u32.
    Error(u16), // u16 -> i32 -> *= -1
}

The request type looks close to the current one but the types of the fields are structs. This allows more type-safety and to implement helper methods.

#[derive(Debug)]
#[non_exhaustive]
pub struct RotifReq {
    pub id: RotifCookie,
    pub pid: RotifPid,
    pub data: RotifData,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RotifCookie(u64, ScmpFd);
impl RotifCookie {
    pub fn is_valid(&self) -> bool {
        notify_id_valid(self.1, self.0).is_ok()
    }

    pub fn ensure_valid(&self) -> std::result::Result<(), &str> {
        if self.is_valid() {
            Ok(())
        } else {
            Err("This notification id has been invalidated.")
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RotifPid(u32);
/*
impl RotifPid {
    fn helper_functions_like() -> File {
        let mem = File::open(&format!("/proc/{}/mem", self.0))?;
        self.id.is_valid().then(|| Some(mem))
    }
}
*/
// PartialEq<u32>; From<u32>; Into<u32>

#[derive(Debug)]
pub struct RotifData {
    pub syscall: ScmpSyscall,
    pub arch: ScmpArch,
    pub instr_pointer: u64,
    pub args: [u64; 6],
}

As a second step the usual "receive, take action and decide, send response"-loop can be abstracted too:

impl Rotify {
    // todo: cancelable
    pub fn receive_and_handle_loop<F>(&self, mut handle_error: F)
    where
        F: FnMut(&mut dyn StdError)
    {
        loop {
            if let Err(mut err) = self.receive_and_handle() {
                handle_error(&mut err);
            }
        }
    }
    
    fn receive_and_handle(&self) -> Result<()> {
        let req = self.receive()?;
        for action in &self.actions {
            if action.should_handle(&req) {
                if let Some(resp) = (action.func)(&req) {
                    self.respond(req.id, resp)?;
                }
            }
        }

        Ok(())
    }

    pub fn add_action<T, F>(&mut self, filter: Option<T>, f: F) -> &mut Self
    where
        //T: RotifFilter + 'static,
        T: Into<Box<dyn RotifFilter>>,
        F: Fn(&RotifReq) -> Option<RotifResp> + 'static,
    {
        self.actions.push(RotifAction {
            //filter: filter.map(|fltr| Box::new(fltr) as _),
            filter: filter.map(Into::into),
            func: Box::new(f),
        });
        self
    }
}

//#[derive(Debug)]
pub struct RotifAction {
    filter: Option<Box<dyn RotifFilter>>,
    func: Box<dyn Fn(&RotifReq) -> Option<RotifResp>>,
}
impl RotifAction {
    fn should_handle(&self, req: &RotifReq) -> bool {
        if let Some(filter) = &self.filter {
            filter.should_handle(req)
        } else {
            true
        }
    }
}

pub trait RotifFilter {
    fn should_handle(&self, req: &RotifReq) -> bool;
}
impl RotifFilter for ScmpSyscall {
    fn should_handle(&self, req: &RotifReq) -> bool {
        *self == req.data.syscall
    }
}
impl RotifFilter for &[ScmpSyscall] {
    fn should_handle(&self, req: &RotifReq) -> bool {
        self.contains(&req.data.syscall)
    }
}
impl<F> RotifFilter for F
where
    F: Fn(&RotifReq) -> bool,
{
    fn should_handle(&self, req: &RotifReq) -> bool {
        (self)(req)
    }
}



Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.