1use crate::{madvise_accepts, madvise_advice, present_syscalls, Target, EINVAL, ENOSYS};
18
19pub const AUDIT_ARCH_X86_64: u32 = 0xc000_003e;
21pub const X32_SYSCALL_BIT: u32 = 0x4000_0000;
23pub const SYS_MADVISE: u32 = 28;
25
26pub const RET_ALLOW: u32 = 0x7fff_0000;
28pub const RET_ERRNO: u32 = 0x0005_0000;
30
31const LD_W_ABS: u16 = 0x20; const JEQ_K: u16 = 0x15; const JGT_K: u16 = 0x25; const JGE_K: u16 = 0x35; const RET_K: u16 = 0x06; const OFF_NR: u32 = 0;
41const OFF_ARCH: u32 = 4;
42const OFF_ARG2_LO: u32 = 16 + 2 * 8;
43
44pub const MAX_INSNS: usize = 4096;
46
47#[repr(C)]
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct Insn {
51 pub code: u16,
53 pub jt: u8,
55 pub jf: u8,
57 pub k: u32,
59}
60
61const fn stmt(code: u16, k: u32) -> Insn {
62 Insn {
63 code,
64 jt: 0,
65 jf: 0,
66 k,
67 }
68}
69
70const fn jump(code: u16, k: u32, jt: u8, jf: u8) -> Insn {
71 Insn { code, jt, jf, k }
72}
73
74pub fn program(target: &Target) -> Vec<Insn> {
80 let enosys = RET_ERRNO | ENOSYS as u32;
81 let einval = RET_ERRNO | EINVAL as u32;
82 let mut p = vec![
83 stmt(LD_W_ABS, OFF_ARCH),
84 jump(JEQ_K, AUDIT_ARCH_X86_64, 1, 0),
85 stmt(RET_K, enosys),
86 stmt(LD_W_ABS, OFF_NR),
87 jump(JGE_K, X32_SYSCALL_BIT, 0, 1),
88 stmt(RET_K, enosys),
89 ];
90
91 let mut nrs: Vec<u32> = present_syscalls(target).iter().map(|s| s.nr).collect();
92 nrs.sort_unstable();
93 nrs.dedup();
94 if nrs.binary_search(&SYS_MADVISE).is_ok() {
95 let accepted: Vec<u32> = madvise_advice()
96 .iter()
97 .map(|a| a.value)
98 .filter(|&v| madvise_accepts(target.kernel, v))
99 .collect();
100 let skip = u8::try_from(accepted.len() + 3).expect("madvise block fits a BPF jump");
103 p.push(jump(JEQ_K, SYS_MADVISE, 0, skip));
104 p.push(stmt(LD_W_ABS, OFF_ARG2_LO));
105 let n = accepted.len();
106 for (i, v) in accepted.iter().enumerate() {
107 let to_allow = u8::try_from(n - i).expect("madvise block fits a BPF jump");
109 p.push(jump(JEQ_K, *v, to_allow, 0));
110 }
111 p.push(stmt(RET_K, einval));
112 p.push(stmt(RET_K, RET_ALLOW));
113 p.push(stmt(LD_W_ABS, OFF_NR));
115 }
116
117 for (lo, hi) in ranges(&nrs) {
118 p.push(jump(JGE_K, lo, 0, 2));
119 p.push(jump(JGT_K, hi, 1, 0));
120 p.push(stmt(RET_K, RET_ALLOW));
121 }
122 p.push(stmt(RET_K, enosys));
123 assert!(p.len() <= MAX_INSNS, "filter exceeds BPF_MAXINSNS");
124 p
125}
126
127fn ranges(sorted: &[u32]) -> Vec<(u32, u32)> {
128 let mut out: Vec<(u32, u32)> = Vec::new();
129 for &n in sorted {
130 match out.last_mut() {
131 Some((_, hi)) if *hi + 1 == n => *hi = n,
132 _ => out.push((n, n)),
133 }
134 }
135 out
136}
137
138pub fn evaluate(prog: &[Insn], arch: u32, nr: u32, args: [u64; 6]) -> Result<u32, String> {
149 let word = |off: u32| -> Result<u32, String> {
150 match off {
151 OFF_NR => Ok(nr),
152 OFF_ARCH => Ok(arch),
153 16..=63 if off.is_multiple_of(4) => {
154 let arg = args[(off as usize - 16) / 8];
155 Ok(if off.is_multiple_of(8) {
156 arg as u32
157 } else {
158 (arg >> 32) as u32
159 })
160 }
161 _ => Err(format!("load from unsupported offset {off}")),
162 }
163 };
164 let mut acc = 0u32;
165 let mut pc = 0usize;
166 while let Some(insn) = prog.get(pc) {
167 pc += 1;
168 let taken = match insn.code {
169 LD_W_ABS => {
170 acc = word(insn.k)?;
171 continue;
172 }
173 RET_K => return Ok(insn.k),
174 JEQ_K => acc == insn.k,
175 JGT_K => acc > insn.k,
176 JGE_K => acc >= insn.k,
177 other => return Err(format!("unsupported opcode {other:#x}")),
178 };
179 pc += usize::from(if taken { insn.jt } else { insn.jf });
180 }
181 Err("program fell off the end".to_string())
182}
183
184#[cfg(target_os = "linux")]
186pub mod install {
187 use super::Insn;
188 use std::io;
189
190 const PR_SET_NO_NEW_PRIVS: i32 = 38;
191 const PR_SET_SECCOMP: i32 = 22;
192 const SECCOMP_MODE_FILTER: u64 = 2;
193
194 #[repr(C)]
196 pub struct Prog<'a> {
197 len: u16,
198 filter: *const Insn,
199 _insns: std::marker::PhantomData<&'a [Insn]>,
200 }
201
202 impl<'a> Prog<'a> {
203 pub fn new(insns: &'a [Insn]) -> Self {
209 Prog {
210 len: u16::try_from(insns.len()).expect("filter fits sock_fprog"),
211 filter: insns.as_ptr(),
212 _insns: std::marker::PhantomData,
213 }
214 }
215 }
216
217 extern "C" {
218 fn prctl(option: i32, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> i32;
219 }
220
221 pub fn apply(prog: &Prog<'_>) -> io::Result<()> {
232 unsafe {
235 if prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 {
236 return Err(io::Error::last_os_error());
237 }
238 let ptr = prog as *const Prog<'_> as u64;
239 if prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ptr, 0, 0) != 0 {
240 return Err(io::Error::last_os_error());
241 }
242 }
243 Ok(())
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use crate::{syscalls, table_ceiling, KernelVersion};
251
252 fn kv(s: &str) -> KernelVersion {
253 KernelVersion::parse(s).unwrap()
254 }
255
256 fn eval(prog: &[Insn], nr: u32, a2: u64) -> u32 {
257 evaluate(prog, AUDIT_ARCH_X86_64, nr, [0, 0, a2, 0, 0, 0]).unwrap()
258 }
259
260 fn assert_matches_data(target: &Target) {
263 let prog = program(target);
264 let present: Vec<u32> = present_syscalls(target).iter().map(|s| s.nr).collect();
265 let max_nr = syscalls().iter().map(|s| s.nr).max().unwrap();
266 for nr in 0..=max_nr + 64 {
267 if nr == SYS_MADVISE {
268 continue;
269 }
270 let want = if present.contains(&nr) {
271 RET_ALLOW
272 } else {
273 RET_ERRNO | ENOSYS as u32
274 };
275 assert_eq!(eval(&prog, nr, 0), want, "{}: nr {nr}", target.label);
276 }
277 let max_advice = madvise_advice().iter().map(|a| a.value).max().unwrap();
278 for v in 0..=max_advice + 8 {
279 let want = if madvise_accepts(target.kernel, v) {
280 RET_ALLOW
281 } else {
282 RET_ERRNO | EINVAL as u32
283 };
284 assert_eq!(eval(&prog, SYS_MADVISE, v as u64), want, "advice {v}");
285 let high = (0xffff_ffffu64 << 32) | v as u64;
287 assert_eq!(
288 eval(&prog, SYS_MADVISE, high),
289 want,
290 "advice {v} + high bits"
291 );
292 }
293 }
294
295 #[test]
296 fn agrees_with_the_data_for_generic_kernels() {
297 for k in crate::GENERIC_KERNELS {
298 assert_matches_data(&Target::kernel(kv(k)));
299 }
300 assert_matches_data(&Target::kernel(table_ceiling()));
301 }
302
303 #[test]
304 fn agrees_with_the_data_for_distro_backports() {
305 for d in crate::distros() {
306 assert_matches_data(&Target::distro(&d));
307 }
308 }
309
310 #[test]
311 fn blocks_other_architectures_and_x32() {
312 let prog = program(&Target::kernel(table_ceiling()));
313 let enosys = RET_ERRNO | ENOSYS as u32;
314 const AUDIT_ARCH_I386: u32 = 0x4000_0003;
315 assert_eq!(evaluate(&prog, AUDIT_ARCH_I386, 0, [0; 6]), Ok(enosys));
316 assert_eq!(eval(&prog, X32_SYSCALL_BIT, 0), enosys);
317 assert_eq!(eval(&prog, X32_SYSCALL_BIT | 1, 0), enosys);
318 assert_eq!(eval(&prog, u32::MAX, 0), enosys);
319 assert_eq!(eval(&prog, 0, 0), RET_ALLOW); }
321
322 #[test]
323 fn stays_well_under_the_instruction_limit() {
324 let len = program(&Target::kernel(kv("3.10"))).len();
325 assert!(len < 512, "{len} instructions");
326 }
327
328 #[test]
329 fn instruction_layout_matches_sock_filter() {
330 assert_eq!(std::mem::size_of::<Insn>(), 8);
331 }
332}