1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use log::*;
use seccomp_sys::*;

// workaround until we can assume libseccomp >= 2.4.0 is always present
include!(concat!(env!("OUT_DIR"), "/const.rs"));

mod rule;
pub use rule::{Cmp, Comparator};

mod error;
pub use error::{Error, Result};

mod syscalls;
pub use syscalls::Syscall;

#[derive(Debug, Clone, Copy)]
pub enum Action {
    KillProcess,
    KillThread,
    Trap,
    Errno(u16),
    Trace(u16),
    Allow,
}

impl Into<u32> for Action {
    fn into(self) -> u32 {
        use self::Action::*;
        match self {
            KillProcess => SCMP_ACT_KILL_PROCESS,
            KillThread => SCMP_ACT_KILL,
            Trap => SCMP_ACT_TRAP,
            Errno(e) => SCMP_ACT_ERRNO(e.into()),
            Trace(t) => SCMP_ACT_TRACE(t.into()),
            Allow => SCMP_ACT_ALLOW,
        }
    }
}

pub struct Context {
    ctx: *mut scmp_filter_ctx,
}

impl Context {
    pub fn init() -> Result<Context> {
        Context::init_with_action(DEFAULT_KILL)
    }

    pub fn init_with_action(default_action: Action) -> Result<Context> {
        let ctx = unsafe { seccomp_init(default_action.into()) };

        if ctx.is_null() {
            return Err(Error::from("seccomp_init returned null".to_string()));
        }

        Ok(Context { ctx })
    }

    #[inline]
    pub fn allow_syscall(&mut self, syscall: Syscall) -> Result<()> {
        self.set_action_for_syscall(Action::Allow, syscall)
    }

    #[inline]
    pub fn set_action_for_syscall(&mut self, action: Action, syscall: Syscall) -> Result<()> {
        debug!("seccomp: setting action={:?} syscall={:?}", action, syscall);
        let ret = unsafe { seccomp_rule_add(self.ctx, action.into(), syscall.into_i32(), 0) };

        if ret != 0 {
            Err(Error::from("seccomp_rule_add returned error".to_string()))
        } else {
            Ok(())
        }
    }

    pub fn set_rule_for_syscall(
        &mut self,
        action: Action,
        syscall: Syscall,
        comparators: &[Comparator],
    ) -> Result<()> {
        debug!(
            "seccomp: setting action={:?} syscall={:?} comparators={:?}",
            action, syscall, comparators
        );
        let comps: Vec<scmp_arg_cmp> = comparators
            .iter()
            .map(|comp| comp.clone().into())
            .collect::<_>();

        let ret = unsafe {
            seccomp_rule_add_array(
                self.ctx,
                action.into(),
                syscall.into_i32(),
                comps.len() as u32,
                comps.as_ptr(),
            )
        };
        if ret != 0 {
            Err(Error::from(
                "seccomp_rule_add_array returned error".to_string(),
            ))
        } else {
            Ok(())
        }
    }

    pub fn load(&self) -> Result<()> {
        debug!("seccomp: loading policy");
        let ret = unsafe { seccomp_load(self.ctx) };

        if ret != 0 {
            Err(Error::from("seccomp_load returned error".to_string()))
        } else {
            Ok(())
        }
    }
}

impl Drop for Context {
    fn drop(&mut self) {
        unsafe { seccomp_release(self.ctx) };
    }
}

#[cfg(test)]
mod tests {
    use super::syscalls::Syscall;
    use super::{Action, Context};
    use libc;

    // this test isn't fully stable yet
    #[test]
    #[ignore]
    fn it_works() {
        let mut ctx = Context::init_with_action(Action::Errno(69)).unwrap();
        ctx.allow_syscall(Syscall::futex).unwrap();
        ctx.load().unwrap();
        assert_eq!(unsafe { libc::getpid() }, -69);
    }

    #[test]
    fn from_name() {
        use crate::syscalls::Syscall;

        let cases = vec![
            ("write", Some(Syscall::write)),
            ("setgid", Some(Syscall::setgid)),
            ("nothing", None),
            ("", None),
        ];

        for (name, rhs) in cases {
            let lhs = Syscall::from_name(name);
            assert_eq!(lhs, rhs);
        }
    }

    #[test]
    fn test_rule() {
        use crate::rule::{Cmp, Comparator};
        use crate::Action;
        use std::fs::File;
        use std::io::Read;
        use std::os::unix::io::AsRawFd;

        let mut f = File::open("Cargo.toml").unwrap();

        let mut ctx = Context::init_with_action(Action::Allow).unwrap();
        ctx.set_rule_for_syscall(
            Action::Errno(1),
            Syscall::read,
            &[Comparator::new(0, Cmp::Eq, f.as_raw_fd() as u64, None)],
        )
        .unwrap();
        ctx.load().unwrap();

        let mut buf: [u8; 1024] = [0; 1024];
        let res = f.read(&mut buf);
        assert!(res.is_err());

        let err = res.unwrap_err();
        assert_eq!(err.raw_os_error(), Some(1));
    }
}