Skip to main content

proot/seccomp/
filter.rs

1//! Builds and installs the classic-BPF seccomp filter.
2//!
3//! Three tiers: syscalls answered by the supervisor (`USER_NOTIF`), syscalls
4//! that need a ptrace stop to edit the tracee in place (`TRACE`), and
5//! everything else (`ALLOW`).  Namespace creation is refused with `EPERM`,
6//! as a default container refuses it, because a tracee in a mount namespace
7//! of its own cannot be served from the supervisor's.  The host's 64-bit ABI
8//! and its 32-bit compat ABI each get a number table; any other audit arch
9//! (x32) is refused with `ENOSYS`.  A compat table may be empty, which
10//! refuses that ABI too.
11//!
12//! The guest-stub restricted mode (see `guest_stub`) traps its trace list
13//! into the guest's `SIGSYS` handler instead, behind a prologue that lets
14//! the stub's own syscalls through by the gate they are made from.
15
16use crate::abi::Abi;
17use crate::guest_stub::layout;
18use crate::sys::*;
19
20/// Mirror of `struct sock_filter`.
21#[repr(C)]
22#[derive(Clone, Copy, Debug)]
23pub struct SockFilter {
24    pub code: u16,
25    pub jt: u8,
26    pub jf: u8,
27    pub k: u32,
28}
29
30/// Mirror of `struct sock_fprog`.
31#[repr(C)]
32pub struct SockFprog {
33    pub len: u16,
34    pub filter: *const SockFilter,
35}
36
37const BPF_LD_W_ABS: u16 = 0x20;
38const BPF_JEQ_K: u16 = 0x15;
39const BPF_JSET_K: u16 = 0x45;
40const BPF_JA: u16 = 0x05;
41const BPF_RET_K: u16 = 0x06;
42
43/// `SECCOMP_RET_TRAP`: raise `SIGSYS` in the calling thread instead.
44const SECCOMP_RET_TRAP: u32 = 0x0003_0000;
45
46/// Offset of the low 32 bits of `seccomp_data.args[i]`.
47fn arg_offset(i: usize) -> u32 {
48    16 + 8 * i as u32
49}
50
51/// One ABI's share of the filter: its audit arch and its number lists.
52pub struct Table<'a> {
53    pub abi: Abi,
54    pub notify: &'a [i64],
55    pub trace: &'a [i64],
56    /// Syscalls whose first argument is refused with `EPERM` when it carries
57    /// a `CLONE_NEW*` bit (`clone`, `unshare`).
58    pub ns_flags: &'a [i64],
59    /// Syscalls refused with `EPERM` outright (`setns`).
60    pub refused: &'a [i64],
61    /// `prctl` numbers whose `PR_SET_DUMPABLE` and `PR_GET_DUMPABLE` go to
62    /// the listener; every other option is allowed.
63    pub dumpable: &'a [i64],
64}
65
66fn stmt(code: u16, k: u32) -> SockFilter {
67    SockFilter {
68        code,
69        jt: 0,
70        jf: 0,
71        k,
72    }
73}
74fn jump(k: u32, jt: u8, jf: u8) -> SockFilter {
75    SockFilter {
76        code: BPF_JEQ_K,
77        jt,
78        jf,
79        k,
80    }
81}
82
83/// A table entry that needs to look at an argument before deciding.
84#[derive(Clone, Copy)]
85enum Sub {
86    /// `open`-family: flags argument `arg` with `O_PATH` set goes to the tracer.
87    OPath { arg: usize },
88    /// `socketcall`: calls 2 (`bind`) and 3 (`connect`) go to the tracer, the rest are allowed.
89    Socketcall,
90    /// `clone`/`unshare`: a `CLONE_NEW*` bit in the first argument is refused.
91    NsFlags,
92    /// `prctl`: `PR_SET_DUMPABLE` and `PR_GET_DUMPABLE` go to the listener, the rest are allowed.
93    Dumpable,
94}
95
96impl Sub {
97    fn len(self) -> usize {
98        match self {
99            Sub::OPath { .. } => 3,
100            Sub::Socketcall => 4,
101            Sub::NsFlags => 3,
102            Sub::Dumpable => 4,
103        }
104    }
105}
106
107/// Builds the filter program.
108///
109/// `O_PATH` opens are routed to the tracer by inspecting the flags argument,
110/// because such descriptors cannot be injected with ADDFD; an i386
111/// `socketcall` is routed there for `bind` and `connect` only.  Without a
112/// tracer (`no_trace`) both go to the listener instead, like every entry of
113/// the trace lists.
114///
115/// # Arguments
116/// * `t64` - The host's 64-bit ABI and its syscall lists.
117/// * `t32` - The host's 32-bit compat ABI and its lists (may be empty).
118/// * `no_trace` - Serve the trace tier through user notification.
119///
120/// # Returns
121/// The instruction list, ready for `seccomp(2)`.
122pub fn build(t64: &Table, t32: &Table, no_trace: bool) -> Vec<SockFilter> {
123    build_tiers(t64, t32, if no_trace { None } else { Some(SECCOMP_RET_TRACE) })
124}
125
126/// Builds the filter of the guest-stub restricted mode.
127///
128/// A syscall made from one of the stub's gates is decided by the gate: the
129/// allow gate's are allowed, the notify gate's go to the listener.  Each ABI
130/// has its gate page at its own address.  Every other `clone3` (435 on both
131/// ABIs) is refused with `ENOSYS`, which makes a C library fall back to
132/// `clone`, whose flags are in a register.  The rest follows [`build`], with
133/// the trace lists trapped into the stub (`SECCOMP_RET_TRAP` with
134/// [`layout::TAG`]) instead of stopping for a tracer.
135///
136/// ```text
137/// 0   ld arch
138/// 1   jeq arch64 -> 3
139/// 2   jeq arch32 -> 8, else -> 15
140/// 3   ld instruction_pointer (high word)
141/// 4   jeq 64-bit gate page -> 5, else -> 15
142/// 5   ld instruction_pointer (low word)
143/// 6   jeq 64-bit allow gate -> 13
144/// 7   jeq 64-bit notify gate -> 14, else -> 15
145/// 8   ld instruction_pointer (high word)
146/// 9   jeq 0 -> 10, else -> 15
147/// 10  ld instruction_pointer (low word)
148/// 11  jeq 32-bit allow gate -> 13
149/// 12  jeq 32-bit notify gate -> 14, else -> 15
150/// 13  ret ALLOW
151/// 14  ret USER_NOTIF
152/// 15  ld nr
153/// 16  jeq clone3 -> 17, else -> T
154/// 17  ret ERRNO(ENOSYS)
155/// T   the tiers of [`build`]
156/// ```
157///
158/// # Arguments
159/// * `t64` - The 64-bit ABI: `notify` answered directly, `trace` trapped.
160/// * `t32` - The compat ABI, likewise.
161///
162/// # Returns
163/// The instruction list, ready for `seccomp(2)`.
164pub fn build_trapped(t64: &Table, t32: &Table) -> Vec<SockFilter> {
165    let ip = |gate: u64| gate + layout::SYSCALL_LEN as u64;
166    let notify = layout::GATE_NOTIFY_OFFSET as u64;
167    let hi64 = (ip(layout::GATE64) >> 32) as u32;
168    debug_assert_eq!(hi64, (ip(layout::GATE64 + notify) >> 32) as u32);
169    let mut f = vec![
170        stmt(BPF_LD_W_ABS, 4),
171        jump(t64.abi.audit(), 1, 0),
172        jump(t32.abi.audit(), 5, 12),
173        stmt(BPF_LD_W_ABS, 12),
174        jump(hi64, 0, 10),
175        stmt(BPF_LD_W_ABS, 8),
176        jump(ip(layout::GATE64) as u32, 6, 0),
177        jump(ip(layout::GATE64 + notify) as u32, 6, 7),
178        stmt(BPF_LD_W_ABS, 12),
179        jump(0, 0, 5),
180        stmt(BPF_LD_W_ABS, 8),
181        jump(ip(layout::GATE32) as u32, 1, 0),
182        jump(ip(layout::GATE32 + notify) as u32, 1, 2),
183        stmt(BPF_RET_K, SECCOMP_RET_ALLOW),
184        stmt(BPF_RET_K, SECCOMP_RET_USER_NOTIF),
185        stmt(BPF_LD_W_ABS, 0),
186        jump(crate::syscall::nr::CLONE3 as u32, 0, 1),
187        stmt(BPF_RET_K, SECCOMP_RET_ERRNO | libc::ENOSYS as u32),
188    ];
189    f.extend(build_tiers(t64, t32, Some(SECCOMP_RET_TRAP | layout::TAG)));
190    f
191}
192
193/// Builds the tiers shared by every mode.
194///
195/// # Arguments
196/// * `t64` - The host's 64-bit ABI and its syscall lists.
197/// * `t32` - The host's 32-bit compat ABI and its lists (may be empty).
198/// * `trace_ret` - What the trace lists return; `None` sends them to the
199///   listener like the notify lists.
200///
201/// # Returns
202/// The instruction list.
203fn build_tiers(t64: &Table, t32: &Table, trace_ret: Option<u32>) -> Vec<SockFilter> {
204    let no_trace = trace_ret.is_none();
205    // Per table: the (nr, sub) entries placed after the plain notify/trace lists.
206    let subs_for = |t: &Table| -> Vec<(i64, Sub)> {
207        let mut v: Vec<(i64, Sub)> = t
208            .notify
209            .iter()
210            .filter_map(|nr| t.abi.open_flags_arg(*nr).map(|arg| (*nr, Sub::OPath { arg })))
211            .collect();
212        if let Some(nr) = t.abi.socketcall_nr() {
213            v.push((nr, Sub::Socketcall));
214        }
215        v.extend(t.ns_flags.iter().map(|nr| (*nr, Sub::NsFlags)));
216        v.extend(t.dumpable.iter().map(|nr| (*nr, Sub::Dumpable)));
217        v
218    };
219    let (subs64, subs32) = (subs_for(t64), subs_for(t32));
220    // A notify entry that has a sub-block is routed through it, not straight to NOTIF.
221    let plain64: Vec<i64> = t64
222        .notify
223        .iter()
224        .copied()
225        .filter(|nr| t64.abi.open_flags_arg(*nr).is_none())
226        .collect();
227    let plain32: Vec<i64> = t32
228        .notify
229        .iter()
230        .copied()
231        .filter(|nr| t32.abi.open_flags_arg(*nr).is_none())
232        .collect();
233    let n64 = plain64.len() + t64.trace.len() + t64.refused.len() + subs64.len();
234    let n32 = plain32.len() + t32.trace.len() + t32.refused.len() + subs32.len();
235    let sub_len: usize = subs64.iter().chain(subs32.iter()).map(|(_, s)| s.len()).sum();
236    let total = 4 + n64 + 2 + n32 + 1 + sub_len + 5;
237    assert!(total < 255, "filter too large for 8-bit jump offsets");
238    // Layout (indices):
239    //   0      ld arch
240    //   1      jeq arch64 -> 3 ; else -> 2
241    //   2      jeq arch32 -> T32 ; else -> E
242    //   3      ld nr
243    //   4..    64-bit table: plain notify, trace, refused, sub entries
244    //   A      ret ALLOW
245    //   T32    ld nr
246    //   T32+1.. 32-bit table
247    //   A2     ret ALLOW
248    //   S..    sub-blocks
249    //   N      ret NOTIF
250    //   T      ret TRACE
251    //   E      ret ERRNO(ENOSYS)
252    //   P      ret ERRNO(EPERM)
253    //   A3     ret ALLOW (for sub-blocks, which cannot jump backwards)
254    let a = 4 + n64;
255    let t32_idx = a + 1;
256    let a2 = t32_idx + 1 + n32;
257    let sub_base = a2 + 1;
258    let n_idx = sub_base + sub_len;
259    let t_idx = if no_trace { n_idx } else { n_idx + 1 };
260    let e_idx = n_idx + 2;
261    let p_idx = e_idx + 1;
262    let a3_idx = e_idx + 2;
263    let off = |from: usize, to: usize| (to - from - 1) as u8;
264    // Start index of sub-block k (over the concatenation of subs64 and subs32).
265    let sub_index = |k: usize| -> usize {
266        sub_base
267            + subs64
268                .iter()
269                .chain(subs32.iter())
270                .take(k)
271                .map(|(_, s)| s.len())
272                .sum::<usize>()
273    };
274    let mut f = Vec::with_capacity(total);
275    f.push(stmt(BPF_LD_W_ABS, 4));
276    f.push(jump(t64.abi.audit(), 1, 0));
277    if n32 == 0 {
278        f.push(jump(t32.abi.audit(), off(2, e_idx), off(2, e_idx)));
279    } else {
280        f.push(jump(t32.abi.audit(), off(2, t32_idx), off(2, e_idx)));
281    }
282    f.push(stmt(BPF_LD_W_ABS, 0));
283    let emit_table = |f: &mut Vec<SockFilter>,
284                      start: usize,
285                      plain: &[i64],
286                      trace: &[i64],
287                      refused: &[i64],
288                      subs: &[(i64, Sub)],
289                      k0: usize| {
290        let mut i = start;
291        for nr in plain {
292            f.push(jump(*nr as u32, off(i, n_idx), 0));
293            i += 1;
294        }
295        for nr in trace {
296            f.push(jump(*nr as u32, off(i, t_idx), 0));
297            i += 1;
298        }
299        for nr in refused {
300            f.push(jump(*nr as u32, off(i, p_idx), 0));
301            i += 1;
302        }
303        for (k, (nr, _)) in subs.iter().enumerate() {
304            f.push(jump(*nr as u32, off(i, sub_index(k0 + k)), 0));
305            i += 1;
306        }
307    };
308    emit_table(&mut f, 4, &plain64, t64.trace, t64.refused, &subs64, 0);
309    f.push(stmt(BPF_RET_K, SECCOMP_RET_ALLOW));
310    f.push(stmt(BPF_LD_W_ABS, 0));
311    emit_table(
312        &mut f,
313        t32_idx + 1,
314        &plain32,
315        t32.trace,
316        t32.refused,
317        &subs32,
318        subs64.len(),
319    );
320    f.push(stmt(BPF_RET_K, SECCOMP_RET_ALLOW));
321    for (k, (_, sub)) in subs64.iter().chain(subs32.iter()).enumerate() {
322        let here = sub_index(k);
323        match *sub {
324            Sub::OPath { arg } => {
325                f.push(stmt(BPF_LD_W_ABS, arg_offset(arg)));
326                f.push(SockFilter {
327                    code: BPF_JSET_K,
328                    jt: off(here + 1, t_idx),
329                    jf: 0,
330                    k: libc::O_PATH as u32,
331                });
332                f.push(SockFilter {
333                    code: BPF_JA,
334                    jt: 0,
335                    jf: 0,
336                    k: off(here + 2, n_idx) as u32,
337                });
338            }
339            Sub::Socketcall => {
340                f.push(stmt(BPF_LD_W_ABS, arg_offset(0)));
341                f.push(jump(2, off(here + 1, t_idx), 0)); // SYS_BIND
342                f.push(jump(3, off(here + 2, t_idx), 0)); // SYS_CONNECT
343                // BPF cannot jump backwards to the table's ALLOW, so the other
344                // calls go forward to the trailing ALLOW.
345                f.push(SockFilter {
346                    code: BPF_JA,
347                    jt: 0,
348                    jf: 0,
349                    k: off(here + 3, a3_idx) as u32,
350                });
351            }
352            Sub::NsFlags => {
353                f.push(stmt(BPF_LD_W_ABS, arg_offset(0)));
354                f.push(SockFilter {
355                    code: BPF_JSET_K,
356                    jt: off(here + 1, p_idx),
357                    jf: 0,
358                    k: crate::syscall::CLONE_NEWNS_FLAGS as u32,
359                });
360                f.push(SockFilter {
361                    code: BPF_JA,
362                    jt: 0,
363                    jf: 0,
364                    k: off(here + 2, a3_idx) as u32,
365                });
366            }
367            Sub::Dumpable => {
368                f.push(stmt(BPF_LD_W_ABS, arg_offset(0)));
369                f.push(jump(libc::PR_SET_DUMPABLE as u32, off(here + 1, n_idx), 0));
370                f.push(jump(libc::PR_GET_DUMPABLE as u32, off(here + 2, n_idx), 0));
371                f.push(SockFilter {
372                    code: BPF_JA,
373                    jt: 0,
374                    jf: 0,
375                    k: off(here + 3, a3_idx) as u32,
376                });
377            }
378        }
379    }
380    f.push(stmt(BPF_RET_K, SECCOMP_RET_USER_NOTIF));
381    f.push(stmt(BPF_RET_K, trace_ret.unwrap_or(SECCOMP_RET_TRACE)));
382    f.push(stmt(BPF_RET_K, SECCOMP_RET_ERRNO | libc::ENOSYS as u32));
383    f.push(stmt(BPF_RET_K, SECCOMP_RET_ERRNO | libc::EPERM as u32));
384    // Trailing ALLOW for sub-blocks that fall through.
385    f.push(stmt(BPF_RET_K, SECCOMP_RET_ALLOW));
386    debug_assert_eq!(f.len(), a3_idx + 1);
387    f
388}
389
390/// Installs `program` in the calling thread and returns the listener fd.
391///
392/// Must run in the child, after `PR_SET_NO_NEW_PRIVS` and `PTRACE_TRACEME`,
393/// before `execve`.  Async-signal-safe: no allocation.
394///
395/// # Arguments
396/// * `program` - Filter from [`build`].
397///
398/// # Errors
399/// Errno from `seccomp(2)`; `EINVAL` on a kernel without user notification.
400pub fn install(program: &[SockFilter]) -> Result<i32> {
401    let prog = SockFprog {
402        len: program.len() as u16,
403        filter: program.as_ptr(),
404    };
405    let fd = unsafe {
406        libc::syscall(
407            libc::SYS_seccomp,
408            SECCOMP_SET_MODE_FILTER,
409            SECCOMP_FILTER_FLAG_NEW_LISTENER,
410            &prog as *const _,
411        )
412    };
413    check(fd).map(|fd| fd as i32)
414}
415
416/// Decodes a raw classic-BPF program as bubblewrap receives it on `--seccomp FD`.
417///
418/// # Arguments
419/// * `bytes` - Concatenated `struct sock_filter` records (8 bytes each).
420///
421/// # Errors
422/// A message when the length is not a multiple of 8, empty, or too long.
423pub fn from_bytes(bytes: &[u8]) -> std::result::Result<Vec<SockFilter>, String> {
424    if bytes.is_empty() || !bytes.len().is_multiple_of(8) {
425        return Err(format!(
426            "{} bytes is not a whole number of BPF instructions",
427            bytes.len()
428        ));
429    }
430    if bytes.len() / 8 > 4096 {
431        return Err("program longer than BPF_MAXINSNS".into());
432    }
433    Ok(bytes
434        .as_chunks::<8>()
435        .0
436        .iter()
437        .map(|c| SockFilter {
438            code: u16::from_ne_bytes([c[0], c[1]]),
439            jt: c[2],
440            jf: c[3],
441            k: u32::from_ne_bytes([c[4], c[5], c[6], c[7]]),
442        })
443        .collect())
444}
445
446/// Installs a caller-supplied program without a listener (stacks on ours).
447///
448/// Must run in the child before `execve`.  Async-signal-safe.
449///
450/// # Arguments
451/// * `program` - Decoded filter.
452pub fn install_plain(program: &[SockFilter]) -> Result<()> {
453    let prog = SockFprog {
454        len: program.len() as u16,
455        filter: program.as_ptr(),
456    };
457    check(unsafe {
458        libc::syscall(
459            libc::SYS_seccomp,
460            SECCOMP_SET_MODE_FILTER,
461            0u64,
462            &prog as *const _,
463        )
464    })
465    .map(|_| ())
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471
472    /// Interprets the subset of BPF we emit, returning the action for (arch, nr, args).
473    fn run_args(prog: &[SockFilter], arch: u32, nr: u32, args: [u64; 6]) -> u32 {
474        run_from(prog, arch, nr, args, 0x0040_1000)
475    }
476
477    /// [`run_args`] for a syscall made at a given instruction pointer.
478    ///
479    /// # Arguments
480    /// * `prog` - The filter.
481    /// * `arch` - `AUDIT_ARCH_*` of the call.
482    /// * `nr` - Syscall number.
483    /// * `args` - Its arguments.
484    /// * `ip` - Instruction pointer of the call.
485    ///
486    /// # Returns
487    /// The filter's action.
488    fn run_from(prog: &[SockFilter], arch: u32, nr: u32, args: [u64; 6], ip: u64) -> u32 {
489        let mut pc = 0usize;
490        let mut acc = 0u32;
491        loop {
492            let i = prog[pc];
493            match i.code {
494                BPF_LD_W_ABS => {
495                    acc = match i.k {
496                        0 => nr,
497                        4 => arch,
498                        8 => ip as u32,
499                        12 => (ip >> 32) as u32,
500                        k => args[((k - 16) / 8) as usize] as u32,
501                    }
502                }
503                BPF_JEQ_K => pc += if acc == i.k { i.jt as usize } else { i.jf as usize },
504                BPF_JSET_K => {
505                    pc += if acc & i.k != 0 {
506                        i.jt as usize
507                    } else {
508                        i.jf as usize
509                    }
510                }
511                BPF_JA => pc += i.k as usize,
512                BPF_RET_K => return i.k,
513                _ => panic!("unknown op"),
514            }
515            pc += 1;
516        }
517    }
518
519    fn run(prog: &[SockFilter], arch: u32, nr: u32) -> u32 {
520        run_args(prog, arch, nr, [0; 6])
521    }
522
523    /// In the guest-stub mode the trace lists are trapped into the stub, the
524    /// notify lists still reach the listener, each ABI's gates are decided
525    /// by the gate whatever the syscall and only from that ABI's page,
526    /// `clone3` gets `ENOSYS`, namespace flags and the refused lists keep
527    /// their `EPERM`, and i386 `socketcall` traps `bind` and `connect` only.
528    #[test]
529    fn trap_mode_routes_by_gate_and_traps_the_staged_calls() {
530        let p = build_trapped(
531            &Table {
532                abi: Abi::X86_64,
533                notify: &[56, 81],
534                trace: &[2, 59, 257],
535                ns_flags: &[272],
536                refused: &[308],
537                dumpable: &[],
538            },
539            &Table {
540                abi: Abi::I386,
541                notify: &[120, 133],
542                trace: &[5, 11, 174],
543                ns_flags: &[310],
544                refused: &[346],
545                dumpable: &[],
546            },
547        );
548        let trap = SECCOMP_RET_TRAP | layout::TAG;
549        let eperm = SECCOMP_RET_ERRNO | libc::EPERM as u32;
550        let enosys = SECCOMP_RET_ERRNO | libc::ENOSYS as u32;
551        let notify = layout::GATE_NOTIFY_OFFSET as u64;
552        let len = layout::SYSCALL_LEN as u64;
553        let (allow64, notify64) = (layout::GATE64 + len, layout::GATE64 + notify + len);
554        let (allow32, notify32) = (layout::GATE32 + len, layout::GATE32 + notify + len);
555        let mark = [layout::HELLO_MARK, 0, 0, 0, 0, 0];
556        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 257), trap);
557        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 59), trap);
558        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 81), SECCOMP_RET_USER_NOTIF);
559        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 56), SECCOMP_RET_USER_NOTIF);
560        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 0), SECCOMP_RET_ALLOW);
561        assert_eq!(
562            run_from(&p, AUDIT_ARCH_X86_64, 257, [0; 6], allow64),
563            SECCOMP_RET_ALLOW
564        );
565        assert_eq!(
566            run_from(&p, AUDIT_ARCH_X86_64, 59, [0; 6], allow64),
567            SECCOMP_RET_ALLOW
568        );
569        assert_eq!(
570            run_from(&p, AUDIT_ARCH_X86_64, 257, [0; 6], notify64),
571            SECCOMP_RET_USER_NOTIF
572        );
573        assert_eq!(
574            run_from(&p, AUDIT_ARCH_X86_64, layout::HELLO64 as u32, mark, notify64),
575            SECCOMP_RET_USER_NOTIF
576        );
577        assert_eq!(run_from(&p, AUDIT_ARCH_X86_64, 257, [0; 6], allow64 + 1), trap);
578        assert_eq!(
579            run_from(&p, AUDIT_ARCH_X86_64, 257, [0; 6], notify64 + (1 << 32)),
580            trap
581        );
582        assert_eq!(run_from(&p, AUDIT_ARCH_X86_64, 257, [0; 6], allow32), trap);
583        assert_eq!(
584            run_args(&p, AUDIT_ARCH_X86_64, 257, [0, 0, 0, 0, 0, 0x4321]),
585            trap
586        );
587        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 435), enosys);
588        let newuser = libc::CLONE_NEWUSER as u64;
589        assert_eq!(
590            run_args(&p, AUDIT_ARCH_X86_64, 272, [newuser, 0, 0, 0, 0, 0]),
591            eperm
592        );
593        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 272), SECCOMP_RET_ALLOW);
594        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 308), eperm);
595        assert_eq!(run(&p, AUDIT_ARCH_I386, 5), trap);
596        assert_eq!(run(&p, AUDIT_ARCH_I386, 11), trap);
597        assert_eq!(run(&p, AUDIT_ARCH_I386, 174), trap);
598        assert_eq!(run(&p, AUDIT_ARCH_I386, 120), SECCOMP_RET_USER_NOTIF);
599        assert_eq!(run(&p, AUDIT_ARCH_I386, 3), SECCOMP_RET_ALLOW);
600        assert_eq!(
601            run_from(&p, AUDIT_ARCH_I386, 5, [0; 6], allow32),
602            SECCOMP_RET_ALLOW
603        );
604        assert_eq!(
605            run_from(&p, AUDIT_ARCH_I386, 5, [0; 6], notify32),
606            SECCOMP_RET_USER_NOTIF
607        );
608        assert_eq!(
609            run_from(&p, AUDIT_ARCH_I386, layout::HELLO32 as u32, mark, notify32),
610            SECCOMP_RET_USER_NOTIF
611        );
612        assert_eq!(run_from(&p, AUDIT_ARCH_I386, 5, [0; 6], allow64), trap);
613        assert_eq!(run_from(&p, AUDIT_ARCH_I386, 5, [0; 6], allow32 + 2), trap);
614        assert_eq!(run_args(&p, AUDIT_ARCH_I386, 102, [3, 0, 0, 0, 0, 0]), trap);
615        assert_eq!(run_args(&p, AUDIT_ARCH_I386, 102, [2, 0, 0, 0, 0, 0]), trap);
616        assert_eq!(
617            run_args(&p, AUDIT_ARCH_I386, 102, [1, 0, 0, 0, 0, 0]),
618            SECCOMP_RET_ALLOW
619        );
620        assert_eq!(run(&p, AUDIT_ARCH_I386, 435), enosys);
621        assert_eq!(
622            run_args(&p, AUDIT_ARCH_I386, 310, [newuser, 0, 0, 0, 0, 0]),
623            eperm
624        );
625        assert_eq!(run(&p, AUDIT_ARCH_I386, 346), eperm);
626    }
627
628    #[test]
629    fn routes_each_tier() {
630        let p = build(
631            &Table {
632                abi: Abi::X86_64,
633                notify: &[2, 257],
634                trace: &[59, 80],
635                ns_flags: &[56, 272],
636                refused: &[308],
637                dumpable: &[157],
638            },
639            &Table {
640                abi: Abi::I386,
641                notify: &[5, 295],
642                trace: &[11, 12],
643                ns_flags: &[120, 310],
644                refused: &[346],
645                dumpable: &[172],
646            },
647            false,
648        );
649        let eperm = SECCOMP_RET_ERRNO | libc::EPERM as u32;
650        assert_eq!(
651            run_args(&p, AUDIT_ARCH_X86_64, 56, [0x1200011, 0, 0, 0, 0, 0]),
652            SECCOMP_RET_ALLOW
653        );
654        assert_eq!(
655            run_args(&p, AUDIT_ARCH_X86_64, 56, [0x10000011, 0, 0, 0, 0, 0]),
656            eperm
657        );
658        assert_eq!(
659            run_args(&p, AUDIT_ARCH_X86_64, 272, [0x20000, 0, 0, 0, 0, 0]),
660            eperm
661        );
662        assert_eq!(
663            run_args(&p, AUDIT_ARCH_X86_64, 272, [0x200, 0, 0, 0, 0, 0]),
664            SECCOMP_RET_ALLOW
665        );
666        assert_eq!(run_args(&p, AUDIT_ARCH_X86_64, 308, [0; 6]), eperm);
667        assert_eq!(
668            run_args(&p, AUDIT_ARCH_I386, 120, [0x10000011, 0, 0, 0, 0, 0]),
669            eperm
670        );
671        assert_eq!(
672            run_args(&p, AUDIT_ARCH_I386, 102, [1, 0, 0, 0, 0, 0]),
673            SECCOMP_RET_ALLOW
674        );
675        assert_eq!(
676            run_args(&p, AUDIT_ARCH_X86_64, 157, [4, 0, 0, 0, 0, 0]),
677            SECCOMP_RET_USER_NOTIF
678        );
679        assert_eq!(
680            run_args(&p, AUDIT_ARCH_X86_64, 157, [3, 0, 0, 0, 0, 0]),
681            SECCOMP_RET_USER_NOTIF
682        );
683        assert_eq!(
684            run_args(&p, AUDIT_ARCH_X86_64, 157, [15, 0, 0, 0, 0, 0]),
685            SECCOMP_RET_ALLOW
686        );
687        assert_eq!(
688            run_args(&p, AUDIT_ARCH_I386, 172, [4, 0, 0, 0, 0, 0]),
689            SECCOMP_RET_USER_NOTIF
690        );
691        assert_eq!(
692            run_args(&p, AUDIT_ARCH_I386, 172, [15, 0, 0, 0, 0, 0]),
693            SECCOMP_RET_ALLOW
694        );
695        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 2), SECCOMP_RET_USER_NOTIF);
696        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 257), SECCOMP_RET_USER_NOTIF);
697        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 59), SECCOMP_RET_TRACE);
698        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 80), SECCOMP_RET_TRACE);
699        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 39), SECCOMP_RET_ALLOW);
700        assert_eq!(run(&p, AUDIT_ARCH_I386, 5), SECCOMP_RET_USER_NOTIF);
701        assert_eq!(run(&p, AUDIT_ARCH_I386, 295), SECCOMP_RET_USER_NOTIF);
702        assert_eq!(run(&p, AUDIT_ARCH_I386, 11), SECCOMP_RET_TRACE);
703        assert_eq!(run(&p, AUDIT_ARCH_I386, 12), SECCOMP_RET_TRACE);
704        assert_eq!(run(&p, AUDIT_ARCH_I386, 2), SECCOMP_RET_ALLOW);
705        assert_eq!(run(&p, 0xc000_00b7, 5), SECCOMP_RET_ERRNO | libc::ENOSYS as u32);
706        // O_PATH opens are traced, plain opens notified, on both ABIs.
707        let opath = libc::O_PATH as u64;
708        assert_eq!(
709            run_args(&p, AUDIT_ARCH_X86_64, 257, [0, 0, opath | 2, 0, 0, 0]),
710            SECCOMP_RET_TRACE
711        );
712        assert_eq!(
713            run_args(&p, AUDIT_ARCH_X86_64, 257, [0, 0, 2, 0, 0, 0]),
714            SECCOMP_RET_USER_NOTIF
715        );
716        assert_eq!(
717            run_args(&p, AUDIT_ARCH_X86_64, 2, [0, opath, 0, 0, 0, 0]),
718            SECCOMP_RET_TRACE
719        );
720        assert_eq!(
721            run_args(&p, AUDIT_ARCH_I386, 295, [0, 0, opath, 0, 0, 0]),
722            SECCOMP_RET_TRACE
723        );
724        assert_eq!(
725            run_args(&p, AUDIT_ARCH_I386, 5, [0, 0, 0, 0, 0, 0]),
726            SECCOMP_RET_USER_NOTIF
727        );
728        // aarch64 host: generic numbers, empty compat table refuses 32-bit ARM.
729        let q = build(
730            &Table {
731                abi: Abi::Aarch64,
732                notify: &[56, 79],
733                trace: &[221],
734                ns_flags: &[],
735                refused: &[],
736                dumpable: &[],
737            },
738            &Table {
739                abi: Abi::Arm,
740                notify: &[],
741                trace: &[],
742                ns_flags: &[],
743                refused: &[],
744                dumpable: &[],
745            },
746            false,
747        );
748        assert_eq!(
749            run_args(&q, AUDIT_ARCH_AARCH64, 56, [0, 0, 0, 0, 0, 0]),
750            SECCOMP_RET_USER_NOTIF
751        );
752        assert_eq!(
753            run_args(&q, AUDIT_ARCH_AARCH64, 56, [0, 0, opath, 0, 0, 0]),
754            SECCOMP_RET_TRACE
755        );
756        assert_eq!(run_args(&q, AUDIT_ARCH_AARCH64, 221, [0; 6]), SECCOMP_RET_TRACE);
757        assert_eq!(run_args(&q, AUDIT_ARCH_AARCH64, 63, [0; 6]), SECCOMP_RET_ALLOW);
758        assert_eq!(
759            run(&q, AUDIT_ARCH_ARM, 5),
760            SECCOMP_RET_ERRNO | libc::ENOSYS as u32
761        );
762    }
763
764    #[test]
765    fn without_a_tracer_everything_is_notified() {
766        let p = build(
767            &Table {
768                abi: Abi::X86_64,
769                notify: &[2, 59, 257],
770                trace: &[],
771                ns_flags: &[272],
772                refused: &[],
773                dumpable: &[],
774            },
775            &Table {
776                abi: Abi::I386,
777                notify: &[5, 11, 295],
778                trace: &[],
779                ns_flags: &[],
780                refused: &[],
781                dumpable: &[],
782            },
783            true,
784        );
785        let opath = libc::O_PATH as u64;
786        assert_eq!(run(&p, AUDIT_ARCH_X86_64, 59), SECCOMP_RET_USER_NOTIF);
787        assert_eq!(
788            run_args(&p, AUDIT_ARCH_X86_64, 257, [0, 0, opath, 0, 0, 0]),
789            SECCOMP_RET_USER_NOTIF
790        );
791        assert_eq!(
792            run_args(&p, AUDIT_ARCH_I386, 102, [3, 0, 0, 0, 0, 0]),
793            SECCOMP_RET_USER_NOTIF
794        );
795        assert_eq!(
796            run_args(&p, AUDIT_ARCH_I386, 102, [1, 0, 0, 0, 0, 0]),
797            SECCOMP_RET_ALLOW
798        );
799        assert_eq!(run(&p, AUDIT_ARCH_I386, 11), SECCOMP_RET_USER_NOTIF);
800    }
801}