seccompiler icon indicating copy to clipboard operation
seccompiler copied to clipboard

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

Open tarka opened this issue 1 year ago • 1 comments

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.

tarka avatar Sep 23 '22 03:09 tarka