rust-vmm / seccompiler

Provides easy-to-use Linux seccomp-bpf jailing.
https://crates.io/crates/seccompiler
Apache License 2.0
77 stars 11 forks source link

Use of BTreeMap can cause inadvertant loss of syscall rules when syscalls duplicated #42

Open tarka opened 2 years ago

tarka commented 2 years ago

In the case where the SeccompFilter rules are generated from a list of (syscall, Vec<SeccompRule) (as in the doc examples), if a syscall is repeated with different args, the later rule will silently overwrite the earlier one. This is a limitation of the Vec->BTreeMap collect implemenation. Given that rules is converted into an iterator over (syscall, Vec<SeccompRule) during try_from the use of a map doesn't seem to add anything.

This has implications for ease-of-use of the library. I came across this issue when implementing a port of OpenBSD pledge(). In pledge() semantics, promises are additive; cpath adds the ability to call open() with O_CREAT, and wpath allows open() with O_WRONLY; to open and then write a file you would need to specify both.

The simplest way to implement this is a separate whitelist for each case, and then chain them together as needed, e.g:

let cpath = vec![(
    libc::SYS_open,
    vec![
        Rule::new(vec![
            Cond::new(
                1,
                ArgLen::Dword,
                CmpOp::MaskedEq(libc::O_CREAT as u64),
                libc::O_CREAT as u64,
            )?])?,
    ])];

let wpath = vec![(
    libc::SYS_open,
    vec![
        Rule::new(vec![Cond::new(
            1,
            ArgLen::Dword,
            CmpOp::MaskedEq(libc::O_ACCMODE as u64),
            libc::O_WRONLY as u64,
        )?])?,
    ],
)];

// This list would actually be based on caller parameters
let rules: Vec<(i64, Vec<Rule>)> = cpath.into_iter()
    .chain(wpath)
    .collect();

let sf = SeccompFilter::new(
    rules.into_iter().collect(),
    Action::KillProcess,
    Action::Allow,
    ARCH.try_into()?,
)?;

However the use of BTreeMap means the cpath rule would be completely overwritten by the wpath rule, and attempts to create the file would fail.

The simplest solution would be to use a Vec for the syscall rule list (i.e. just skip the BTreeMap conversion) and leave the partitioning of the syscall rules up the caller. I've already tried this with v0.3.0 and it works fine; I'm happy to raise a PR. However it would obviously be a breaking change for current users of the lib.

alindima commented 1 year ago

Hi,

I believe this is a fundamental problem with the way you are converting from a Vec to a BTreeMap. SeccompFilter::new clearly expects a BTreeMap so I don't think there's an issue with the library. You should create the BTreeMap in the correct manner (by appending to the vector of rules if the key already exists in the map).

I agree though that this could be a quality of life improvement, but I would suggest making this generic over something that implements IntoIterator.

However I'm not sure it's worth breaking existing users