1use crate::mm::MemoryAccessorExt;
6use crate::signals::{SignalDetail, SignalInfo, send_standard_signal};
7use crate::task::{
8 CurrentTask, EventHandler, ExitStatus, Kernel, Task, TaskFlags, WaitCanceler, WaitQueue, Waiter,
9};
10use crate::vfs::buffers::{InputBuffer, OutputBuffer};
11use crate::vfs::{
12 Anon, FdFlags, FdNumber, FileObject, FileObjectState, FileOps, fileops_impl_nonseekable,
13 fileops_impl_noop_sync,
14};
15use bstr::ByteSlice;
16use ebpf::{
17 BPF_A, BPF_ABS, BPF_IMM, BPF_IND, BPF_JA, BPF_JEQ, BPF_JGE, BPF_JGT, BPF_JMP, BPF_JSET, BPF_K,
18 BPF_LD, BPF_RET, BPF_SRC_MASK, BPF_ST, BPF_SUB_OP_MASK, BPF_W, BpfProgramContext, CbpfConfig,
19 EbpfProgram, MemoryId, NoMap, PointerOrigin, ProgramArgument, Type, bpf_addressing_mode,
20 bpf_class, bpf_size, convert_and_link_cbpf,
21};
22use ebpf_api::SECCOMP_CBPF_CONFIG;
23use linux_uapi::AUDIT_SECCOMP;
24use starnix_logging::{log_warn, track_stub};
25use starnix_sync::{LockDepMutex, SeccompNotifierLock};
26use starnix_syscalls::decls::Syscall;
27use starnix_syscalls::{SyscallArg, SyscallResult};
28use starnix_uapi::errors::Errno;
29use starnix_uapi::open_flags::OpenFlags;
30use starnix_uapi::signals::{SIGKILL, SIGSYS};
31#[cfg(target_arch = "aarch64")]
32use starnix_uapi::user_address::ArchSpecific;
33use starnix_uapi::user_address::{UserAddress, UserRef};
34use starnix_uapi::vfs::FdEvents;
35use starnix_uapi::{
36 __NR_exit, __NR_read, __NR_write, SECCOMP_IOCTL_NOTIF_ADDFD, SECCOMP_IOCTL_NOTIF_ID_VALID,
37 SECCOMP_IOCTL_NOTIF_RECV, SECCOMP_IOCTL_NOTIF_SEND, SECCOMP_MODE_DISABLED, SECCOMP_MODE_FILTER,
38 SECCOMP_MODE_STRICT, SECCOMP_RET_ACTION_FULL, SECCOMP_RET_DATA,
39 SECCOMP_USER_NOTIF_FLAG_CONTINUE, SYS_SECCOMP, errno, errno_from_code, error, seccomp_data,
40 seccomp_notif, seccomp_notif_resp, sock_filter,
41};
42use std::collections::HashMap;
43use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
44use std::sync::{Arc, LazyLock};
45use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
46
47#[cfg(target_arch = "aarch64")]
48use starnix_uapi::__NR_clock_getres;
49#[cfg(target_arch = "aarch64")]
50use starnix_uapi::__NR_clock_gettime;
51#[cfg(target_arch = "aarch64")]
52use starnix_uapi::__NR_gettimeofday;
53#[cfg(target_arch = "aarch64")]
54use starnix_uapi::AUDIT_ARCH_AARCH64;
55#[cfg(target_arch = "aarch64")]
56use starnix_uapi::AUDIT_ARCH_ARM;
57
58#[cfg(target_arch = "x86_64")]
59use starnix_uapi::__NR_clock_gettime;
60#[cfg(target_arch = "x86_64")]
61use starnix_uapi::__NR_getcpu;
62#[cfg(target_arch = "x86_64")]
63use starnix_uapi::__NR_gettimeofday;
64#[cfg(target_arch = "x86_64")]
65use starnix_uapi::__NR_time;
66#[cfg(target_arch = "x86_64")]
67use starnix_uapi::AUDIT_ARCH_X86_64;
68
69#[cfg(target_arch = "riscv64")]
70use starnix_uapi::{__NR_clock_gettime, AUDIT_ARCH_RISCV64};
71
72const NATIVE_ALWAYS_ALLOW_SYSCALLS: usize = 512;
73const NATIVE_ALWAYS_ALLOW_WORDS: usize = NATIVE_ALWAYS_ALLOW_SYSCALLS / 64;
74
75#[cfg(target_arch = "x86_64")]
76const NATIVE_AUDIT_ARCH: u32 = AUDIT_ARCH_X86_64;
77#[cfg(target_arch = "aarch64")]
78const NATIVE_AUDIT_ARCH: u32 = AUDIT_ARCH_AARCH64;
79#[cfg(target_arch = "riscv64")]
80const NATIVE_AUDIT_ARCH: u32 = AUDIT_ARCH_RISCV64;
81
82pub struct SeccompFilter {
83 program: EbpfProgram<SeccompFilter>,
85
86 unique_id: u64,
91
92 log: bool,
94
95 pub notifier: Option<SeccompNotifierHandle>,
97
98 always_allow: [u64; NATIVE_ALWAYS_ALLOW_WORDS],
101}
102
103pub struct SeccompFilterResult {
105 action: SeccompAction,
107
108 filter: Option<Arc<SeccompFilter>>,
111}
112
113fn cbpf_unconditionally_allows_native(code: &[sock_filter], nr: u32) -> bool {
116 const NR_OFFSET: u32 = std::mem::offset_of!(seccomp_data, nr) as u32;
117 const ARCH_OFFSET: u32 = std::mem::offset_of!(seccomp_data, arch) as u32;
118 const BPF_RVAL_MASK: u8 = BPF_K | BPF_A | BPF_SRC_MASK;
119
120 let mut pc = 0usize;
121 let mut a = 0u32;
122 while pc < code.len() {
123 let insn = &code[pc];
124 match bpf_class(insn) {
125 BPF_LD => {
126 if bpf_size(insn) != BPF_W {
127 return false;
128 }
129 match bpf_addressing_mode(insn) {
130 BPF_ABS => {
131 match insn.k {
132 NR_OFFSET => a = nr,
133 ARCH_OFFSET => a = NATIVE_AUDIT_ARCH,
134 _ => return false,
135 }
136 pc += 1;
137 }
138 BPF_IMM => {
139 a = insn.k;
140 pc += 1;
141 }
142 _ => return false,
143 }
144 }
145 BPF_JMP => {
146 let op = (insn.code as u8) & BPF_SUB_OP_MASK;
147 let src = (insn.code as u8) & BPF_SRC_MASK;
148 if op == BPF_JA {
149 pc = pc.saturating_add(1).saturating_add(insn.k as usize);
150 } else if src == BPF_K {
151 let k = insn.k;
152 let cond = match op {
153 BPF_JEQ => a == k,
154 BPF_JGT => a > k,
155 BPF_JGE => a >= k,
156 BPF_JSET => (a & k) != 0,
157 _ => return false,
158 };
159 let offset = if cond { insn.jt as usize } else { insn.jf as usize };
160 pc = pc.saturating_add(1).saturating_add(offset);
161 } else {
162 return false;
163 }
164 }
165 BPF_RET => {
166 let rval = (insn.code as u8) & BPF_RVAL_MASK;
167 let ret = match rval {
168 BPF_K => insn.k,
169 BPF_A => a,
170 _ => return false,
171 };
172 return SeccompAction::from_u32(ret) == Some(SeccompAction::Allow);
173 }
174 _ => return false,
175 }
176 }
177 false
178}
179
180fn compute_always_allow_mask(code: &[sock_filter]) -> [u64; NATIVE_ALWAYS_ALLOW_WORDS] {
181 let mut mask = [0u64; NATIVE_ALWAYS_ALLOW_WORDS];
182 for nr in 0..(NATIVE_ALWAYS_ALLOW_SYSCALLS as u32) {
183 if cbpf_unconditionally_allows_native(code, nr) {
184 mask[(nr as usize) / 64] |= 1u64 << (nr % 64);
185 }
186 }
187 mask
188}
189
190impl SeccompFilter {
191 pub fn from_cbpf(
194 code: &Vec<sock_filter>,
195 maybe_unique_id: u64,
196 should_log: bool,
197 notifier: Option<SeccompNotifierHandle>,
198 ) -> Result<Self, Errno> {
199 for insn in code {
200 if (bpf_class(insn) == BPF_LD || bpf_class(insn) == BPF_ST)
203 && (bpf_addressing_mode(insn) == BPF_ABS)
204 && (insn.k & 0x3 != 0 || insn.k as usize >= std::mem::size_of::<seccomp_data>())
205 {
206 return error!(EINVAL);
207 }
208 if (bpf_class(insn) == BPF_LD || bpf_class(insn) == BPF_ST)
210 && bpf_addressing_mode(insn) == BPF_IND
211 {
212 return error!(EINVAL);
213 }
214 if (bpf_class(insn) == BPF_LD || bpf_class(insn) == BPF_ST) && bpf_size(insn) != BPF_W {
216 return error!(EINVAL);
217 }
218 }
219
220 let program = convert_and_link_cbpf::<SeccompFilter>(code).map_err(|errmsg| {
221 log_warn!("{}", errmsg);
222 errno!(EINVAL)
223 })?;
224 let always_allow = compute_always_allow_mask(code);
225
226 Ok(SeccompFilter {
227 program,
228 unique_id: maybe_unique_id,
229 log: should_log,
230 notifier,
231 always_allow,
232 })
233 }
234
235 pub fn run(&self, data: &seccomp_data) -> u32 {
236 self.program.run(&mut (), &SeccompData(*data)) as u32
237 }
238}
239
240#[repr(C)]
242#[derive(Debug, Default, Clone, IntoBytes, FromBytes, KnownLayout, Immutable)]
243pub struct SeccompData(seccomp_data);
244
245impl BpfProgramContext for SeccompFilter {
246 type RunContext<'a> = ();
247 type Packet<'a> = &'a SeccompData;
248 type Map = NoMap;
249 const CBPF_CONFIG: &'static CbpfConfig = &SECCOMP_CBPF_CONFIG;
250}
251
252ebpf::empty_static_helper_set!(SeccompFilter);
253
254static SECCOMP_DATA_TYPE: LazyLock<Type> = LazyLock::new(|| Type::PtrToMemory {
255 id: MemoryId::new(),
256 offset: 0.into(),
257 buffer_size: 0,
258 origin: PointerOrigin::KernelReadOnly,
259});
260
261impl ProgramArgument for &'_ SeccompData {
262 fn get_type() -> &'static Type {
263 &*SECCOMP_DATA_TYPE
264 }
265}
266
267const SECCOMP_MAX_INSNS_PER_PATH: u16 = 32768;
268
269pub struct SeccompFilterContainer {
271 pub filters: Vec<Arc<SeccompFilter>>,
273
274 pub provided_instructions: u16,
279
280 pub always_allow: [u64; NATIVE_ALWAYS_ALLOW_WORDS],
282}
283
284impl Default for SeccompFilterContainer {
285 fn default() -> Self {
286 Self {
287 filters: Vec::new(),
288 provided_instructions: 0,
289 always_allow: [u64::MAX; NATIVE_ALWAYS_ALLOW_WORDS],
290 }
291 }
292}
293
294impl Clone for SeccompFilterContainer {
295 fn clone(&self) -> Self {
296 for filter in &self.filters {
297 if let Some(n) = &filter.notifier {
298 n.lock().add_thread();
299 }
300 }
301 SeccompFilterContainer {
302 filters: self.filters.clone(),
303 provided_instructions: self.provided_instructions,
304 always_allow: self.always_allow,
305 }
306 }
307}
308
309impl Drop for SeccompFilterContainer {
310 fn drop(&mut self) {
311 for filter in &self.filters {
312 if let Some(n) = &filter.notifier {
313 n.lock().remove_thread();
316 }
317 }
318 }
319}
320
321fn make_seccomp_data(
322 #[allow(unused_variables)] current_task: &CurrentTask,
323 syscall: &Syscall,
324 ip: u64,
325) -> seccomp_data {
326 #[cfg(target_arch = "aarch64")]
327 let arch_val = if current_task.is_arch32() { AUDIT_ARCH_ARM } else { NATIVE_AUDIT_ARCH };
328 #[cfg(not(target_arch = "aarch64"))]
329 let arch_val = NATIVE_AUDIT_ARCH;
330 seccomp_data {
331 nr: syscall.decl.number as i32,
332 arch: arch_val,
333 instruction_pointer: ip,
334 args: [
335 syscall.arg0.raw(),
336 syscall.arg1.raw(),
337 syscall.arg2.raw(),
338 syscall.arg3.raw(),
339 syscall.arg4.raw(),
340 syscall.arg5.raw(),
341 ],
342 }
343}
344
345fn is_vdso_clock(clock_id: u64) -> bool {
346 let Ok(clock_id) = u32::try_from(clock_id) else {
347 return false;
348 };
349 matches!(
350 clock_id,
351 ::starnix_uapi::CLOCK_REALTIME
352 | ::starnix_uapi::CLOCK_MONOTONIC
353 | ::starnix_uapi::CLOCK_PROCESS_CPUTIME_ID
354 | ::starnix_uapi::CLOCK_THREAD_CPUTIME_ID
355 | ::starnix_uapi::CLOCK_MONOTONIC_RAW
356 | ::starnix_uapi::CLOCK_REALTIME_COARSE
357 | ::starnix_uapi::CLOCK_MONOTONIC_COARSE
358 | ::starnix_uapi::CLOCK_BOOTTIME
359 )
360}
361
362impl SeccompFilterContainer {
363 pub fn can_sync_to(&self, source: &SeccompFilterContainer) -> bool {
366 if source.filters.len() < self.filters.len() {
367 return false;
368 }
369 for (filter, other_filter) in self.filters.iter().zip(source.filters.iter()) {
370 if other_filter.unique_id != filter.unique_id {
371 return false;
372 }
373 }
374 true
375 }
376
377 pub fn add_filter(
381 &mut self,
382 filter: Arc<SeccompFilter>,
383 original_length: u16,
384 ) -> Result<(), Errno> {
385 let maybe_new_length = self.provided_instructions + original_length + 4;
386 if maybe_new_length > SECCOMP_MAX_INSNS_PER_PATH {
387 return error!(ENOMEM);
388 }
389
390 for i in 0..NATIVE_ALWAYS_ALLOW_WORDS {
391 self.always_allow[i] &= filter.always_allow[i];
392 }
393 self.provided_instructions = maybe_new_length;
394 self.filters.push(filter);
395 Ok(())
396 }
397
398 pub fn run_all(&self, current_task: &CurrentTask, syscall: &Syscall) -> SeccompFilterResult {
401 let mut r = SeccompFilterResult { action: SeccompAction::Allow, filter: None };
402
403 #[cfg(target_arch = "x86_64")] #[allow(non_upper_case_globals)]
408 if syscall.decl.number as u32 == __NR_clock_gettime {
409 if is_vdso_clock(syscall.arg0.raw()) {
410 return r;
411 }
412 } else if let __NR_getcpu | __NR_gettimeofday | __NR_time = syscall.decl.number as u32 {
413 return r;
414 }
415 #[cfg(target_arch = "aarch64")]
416 #[allow(non_upper_case_globals)]
417 if !current_task.is_arch32() {
418 if syscall.decl.number as u32 == __NR_clock_gettime
419 || syscall.decl.number as u32 == __NR_clock_getres
420 {
421 if is_vdso_clock(syscall.arg0.raw()) {
422 return r;
423 }
424 } else if let __NR_gettimeofday = syscall.decl.number as u32 {
425 return r;
426 }
427 }
428 #[cfg(target_arch = "riscv64")]
429 #[allow(non_upper_case_globals)]
430 if syscall.decl.number as u32 == __NR_clock_gettime {
431 if is_vdso_clock(syscall.arg0.raw()) {
432 return r;
433 }
434 }
435
436 let data = make_seccomp_data(
437 current_task,
438 syscall,
439 current_task.thread_state.registers.instruction_pointer_register(),
440 );
441
442 for filter in self.filters.iter().rev() {
444 let new_result = filter.run(&data);
445
446 let action = SeccompAction::from_u32(new_result).unwrap_or(SeccompAction::KillProcess);
447
448 if SeccompAction::has_prio(&action, &r.action) == std::cmp::Ordering::Less {
449 r = SeccompFilterResult { action, filter: Some(filter.clone()) };
450 }
451 }
452 r
453 }
454
455 pub fn create_notifier() -> SeccompNotifierHandle {
456 SeccompNotifier::new()
457 }
458
459 pub fn register_listener(
460 current_task: &CurrentTask,
461 notifier: SeccompNotifierHandle,
462 ) -> Result<FdNumber, Errno> {
463 let handle = Anon::new_file(
466 current_task,
467 Box::new(SeccompNotifierFileObject { notifier: notifier.clone() }),
468 OpenFlags::RDWR,
469 "seccomp notify",
470 )?;
471
472 let filters = &mut current_task.write().seccomp_filters;
474 if filters.filters.iter().any(|f| f.notifier.is_some()) {
475 return error!(EBUSY);
476 }
477 let fd = current_task.add_file(handle, FdFlags::CLOEXEC)?;
478 {
479 let mut state = notifier.lock();
480 state.add_thread();
481 }
482 Ok(fd)
483 }
484}
485
486#[repr(u8)]
489#[derive(Clone, Copy, PartialEq, Debug)]
490pub enum SeccompStateValue {
491 None = SECCOMP_MODE_DISABLED as u8,
492 Strict = SECCOMP_MODE_STRICT as u8,
493 UserDefined = SECCOMP_MODE_FILTER as u8,
494}
495
496#[derive(Default)]
498pub struct SeccompState {
499 filter_state: AtomicU8,
501 always_allow: [AtomicU64; NATIVE_ALWAYS_ALLOW_WORDS],
502}
503
504impl SeccompState {
505 pub fn from(state: &SeccompState) -> SeccompState {
506 SeccompState {
507 filter_state: AtomicU8::new(state.filter_state.load(Ordering::Acquire)),
508 always_allow: std::array::from_fn(|i| {
509 AtomicU64::new(state.always_allow[i].load(Ordering::Relaxed))
510 }),
511 }
512 }
513
514 pub fn set_always_allow(&self, mask: &[u64; NATIVE_ALWAYS_ALLOW_WORDS]) {
515 for i in 0..NATIVE_ALWAYS_ALLOW_WORDS {
516 self.always_allow[i].store(mask[i], Ordering::Relaxed);
517 }
518 }
519
520 #[inline]
521 pub fn is_syscall_always_allowed(&self, nr: u64) -> bool {
522 if nr >= NATIVE_ALWAYS_ALLOW_SYSCALLS as u64 {
523 return false;
524 }
525 let word = self.always_allow[(nr as usize) / 64].load(Ordering::Relaxed);
526 (word & (1u64 << (nr % 64))) != 0
527 }
528
529 fn from_u8(value: u8) -> SeccompStateValue {
530 match value {
531 v if v == SECCOMP_MODE_DISABLED as u8 => SeccompStateValue::None,
532 v if v == SECCOMP_MODE_STRICT as u8 => SeccompStateValue::Strict,
533 v if v == SECCOMP_MODE_FILTER as u8 => SeccompStateValue::UserDefined,
534 _ => unreachable!(),
535 }
536 }
537
538 pub fn get(&self) -> SeccompStateValue {
539 Self::from_u8(self.filter_state.load(Ordering::Acquire))
540 }
541
542 pub fn set(&self, state: &SeccompStateValue) -> Result<(), Errno> {
543 loop {
544 let seccomp_filter_status = self.get();
545 if seccomp_filter_status == *state {
546 self.filter_state.store(*state as u8, Ordering::Release);
551 return Ok(());
552 }
553 if seccomp_filter_status != SeccompStateValue::None {
554 return error!(EINVAL);
555 }
556
557 if *state != SeccompStateValue::UserDefined {
558 self.set_always_allow(&[0u64; NATIVE_ALWAYS_ALLOW_WORDS]);
559 }
560
561 if self
562 .filter_state
563 .compare_exchange(
564 seccomp_filter_status as u8,
565 *state as u8,
566 Ordering::Release,
567 Ordering::Acquire,
568 )
569 .is_ok()
570 {
571 return Ok(());
572 }
573 }
574 }
575
576 pub fn do_strict(task: &Task, syscall: &Syscall) -> Option<Result<SyscallResult, Errno>> {
579 if syscall.decl.number as u32 != __NR_exit
580 && syscall.decl.number as u32 != __NR_read
581 && syscall.decl.number as u32 != __NR_write
582 {
583 send_standard_signal(task, SignalInfo::forced(SIGKILL));
584 return Some(Err(errno_from_code!(0)));
585 }
586 None
587 }
588
589 fn log_action(task: &CurrentTask, syscall: &Syscall) {
593 let creds = task.current_creds();
594 let (uid, gid) = (creds.uid, creds.gid);
595 let arch = if cfg!(target_arch = "x86_64") {
596 "x86_64"
597 } else if cfg!(target_arch = "aarch64") {
598 "aarch64"
599 } else {
600 "unknown"
601 };
602 task.kernel().audit_logger().audit_log(AUDIT_SECCOMP as u16, || {
603 format!(
604 "uid={} gid={} pid={} comm={} syscall={} ip={} ARCH={} SYSCALL={}",
605 uid,
606 gid,
607 task.pid,
608 task.command(),
609 syscall.decl.number,
610 task.thread_state.registers.instruction_pointer_register(),
611 arch,
612 syscall.decl.name(),
613 )
614 });
615 }
616
617 #[allow(clippy::wildcard_in_or_patterns)]
624 pub fn do_user_defined(
625 result: SeccompFilterResult,
626 current_task: &mut CurrentTask,
627 syscall: &Syscall,
628 ) -> Option<Result<SyscallResult, Errno>> {
629 let action = result.action;
630 if let Some(filter) = result.filter.as_ref() {
631 if action.is_logged(current_task.kernel(), filter.log) {
632 Self::log_action(current_task, syscall);
633 }
634 }
635 match action {
636 SeccompAction::Allow => None,
637 SeccompAction::Errno(code) => Some(Err(errno_from_code!(code as i16))),
638 SeccompAction::KillThread => {
639 let siginfo = SignalInfo::kernel(SIGSYS);
640
641 let is_last_thread = current_task.thread_group().read().tasks_count() == 1;
642 let mut task_state = current_task.write();
643
644 if is_last_thread {
645 task_state.set_flags(TaskFlags::DUMP_ON_EXIT, true);
646 task_state.set_exit_status_if_not_already(ExitStatus::CoreDump(siginfo));
647 } else {
648 task_state.set_exit_status_if_not_already(ExitStatus::Kill(siginfo));
649 }
650 Some(Err(errno_from_code!(0)))
651 }
652 SeccompAction::KillProcess => {
653 current_task.kill_thread_group(ExitStatus::CoreDump(SignalInfo::kernel(SIGSYS)));
654 Some(Err(errno_from_code!(0)))
655 }
656 SeccompAction::Log => {
657 Self::log_action(current_task, syscall);
658 None
659 }
660 SeccompAction::Trace => {
661 track_stub!(TODO("https://fxbug.dev/297311898"), "ptrace seccomp support");
662 Some(error!(ENOSYS))
663 }
664 SeccompAction::Trap(errno) => {
665 #[cfg(target_arch = "x86_64")]
666 let arch_val = AUDIT_ARCH_X86_64;
667 #[cfg(target_arch = "aarch64")]
668 let arch_val =
669 if current_task.is_arch32() { AUDIT_ARCH_ARM } else { AUDIT_ARCH_AARCH64 };
670 #[cfg(target_arch = "riscv64")]
671 let arch_val = AUDIT_ARCH_RISCV64;
672
673 let siginfo = SignalInfo::new(
674 SIGSYS,
675 errno as i32,
676 SYS_SECCOMP as i32,
677 SignalDetail::SIGSYS {
678 call_addr: current_task
679 .thread_state
680 .registers
681 .instruction_pointer_register()
682 .into(),
683 syscall: syscall.decl.number as i32,
684 arch: arch_val,
685 },
686 true,
687 None,
688 );
689
690 send_standard_signal(current_task, siginfo);
691 Some(Err(errno_from_code!(-(syscall.decl.number as i16))))
692 }
693 SeccompAction::UserNotif => {
694 if let Some(notifier) = result.filter.as_ref().and_then(|f| f.notifier.clone()) {
695 let waiter = Waiter::new();
696 let cookie;
697 {
698 let mut notifier = notifier.lock();
699 if notifier.is_closed {
700 return Some(error!(ENOSYS));
701 }
702 cookie = notifier.next_cookie();
703 let msg = seccomp_notif {
704 id: cookie,
705 pid: current_task.tid.id as u32,
706 flags: 0,
707 data: make_seccomp_data(
708 current_task,
709 syscall,
710 current_task.thread_state.registers.instruction_pointer_register(),
711 ),
712 };
713 notifier.create_notification(cookie, msg);
714 notifier.waiters.wait_async_value(&waiter, cookie);
715 }
716
717 if let Err(e) = waiter.wait(current_task) {
719 return Some(Err(e));
720 }
721
722 let resp: Option<seccomp_notif_resp>;
724 {
725 let mut notifier = notifier.lock();
726 resp = notifier.get_response(cookie);
727 notifier.delete_notification(cookie);
728 }
729
730 if let Some(response) = resp {
732 if response.val != 0 {
733 return Some(Ok(response.val.into()));
734 }
735 if response.error != 0 {
736 if response.error > 0 {
737 return Some(Ok(response.error.into()));
738 } else {
739 return Some(Err(errno_from_code!(-response.error as i16)));
740 }
741 }
742 if response.flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE != 0 {
743 return None;
744 }
745 }
746 Some(Ok(0.into()))
747 } else {
748 Some(error!(ENOSYS))
749 }
750 }
751 }
752 }
753}
754
755#[derive(Clone, Copy, PartialEq)]
756pub enum SeccompAction {
757 Allow,
758 Errno(u32),
759 KillProcess,
760 KillThread,
761 Log,
762 Trap(u32),
763 Trace,
764 UserNotif,
765}
766
767impl SeccompAction {
768 pub fn is_action_available(action: u32) -> Result<SyscallResult, Errno> {
769 if SeccompAction::from_u32(action).is_none() {
770 return error!(EOPNOTSUPP);
771 }
772 Ok(0.into())
773 }
774
775 pub fn from_u32(action: u32) -> Option<SeccompAction> {
776 match action & !SECCOMP_RET_DATA {
777 linux_uapi::SECCOMP_RET_ALLOW => Some(Self::Allow),
778 linux_uapi::SECCOMP_RET_ERRNO => {
779 let mut action = action & SECCOMP_RET_DATA;
780 action = std::cmp::min(action & 0xffff, 0xfff);
782 Some(Self::Errno(action))
783 }
784 linux_uapi::SECCOMP_RET_KILL_PROCESS => Some(Self::KillProcess),
785 linux_uapi::SECCOMP_RET_KILL_THREAD => Some(Self::KillThread),
786 linux_uapi::SECCOMP_RET_LOG => Some(Self::Log),
787 linux_uapi::SECCOMP_RET_TRACE => Some(Self::Trace),
788 linux_uapi::SECCOMP_RET_TRAP => Some(Self::Trap(action & SECCOMP_RET_DATA)),
789
790 linux_uapi::SECCOMP_RET_USER_NOTIF => Some(Self::UserNotif),
791 _ => None,
792 }
793 }
794
795 pub fn to_isize(self) -> isize {
796 match self {
797 Self::Allow => linux_uapi::SECCOMP_RET_ALLOW as isize,
798 Self::Errno(x) => (linux_uapi::SECCOMP_RET_ERRNO | x) as isize,
799 Self::KillProcess => linux_uapi::SECCOMP_RET_KILL_PROCESS as isize,
800 Self::KillThread => linux_uapi::SECCOMP_RET_KILL_THREAD as isize,
801 Self::Log => linux_uapi::SECCOMP_RET_LOG as isize,
802 Self::Trace => linux_uapi::SECCOMP_RET_TRACE as isize,
803 Self::Trap(x) => (linux_uapi::SECCOMP_RET_TRAP | x) as isize,
804 Self::UserNotif => linux_uapi::SECCOMP_RET_USER_NOTIF as isize,
805 }
806 }
807
808 pub fn canonical_name(self) -> &'static str {
809 match self {
810 Self::Allow => &"allow",
811 Self::Errno(_) => &"errno",
812 Self::KillProcess => &"kill_process",
813 Self::KillThread => &"kill_thread",
814 Self::Log => &"log",
815 Self::Trace => &"trace",
816 Self::Trap(_) => &"trap",
817 Self::UserNotif => &"user_notif",
818 }
819 }
820
821 pub fn has_prio(a: &SeccompAction, b: &SeccompAction) -> std::cmp::Ordering {
822 let anum = a.to_isize() as i32;
823 let bnum = b.to_isize() as i32;
824 let fullnum = SECCOMP_RET_ACTION_FULL as i32;
825 let aval = anum & fullnum;
826 let bval = bnum & fullnum;
827 aval.cmp(&bval)
828 }
829
830 pub fn all_actions() -> Vec<SeccompAction> {
832 let mut result = vec![
833 Self::Allow,
834 Self::Errno(0),
835 Self::KillProcess,
836 Self::KillThread,
837 Self::Log,
838 Self::Trace,
839 Self::Trap(0),
840 Self::UserNotif,
841 ];
842
843 result.sort_by(Self::has_prio);
844 result
845 }
846
847 pub fn get_actions_avail_file() -> Vec<u8> {
849 let all_actions = Self::all_actions();
850 if all_actions.len() == 0 {
851 return vec![];
852 }
853 let mut result = String::from(all_actions[0].canonical_name());
854 for i in 1..all_actions.len() {
855 result.push_str(" ");
856 result.push_str(all_actions[i].canonical_name());
857 }
858 result.push('\n');
859 result.into_bytes()
860 }
861
862 fn logged_bit_offset(&self) -> u32 {
863 match self {
864 Self::Allow => 1,
865 Self::Errno(_) => 2,
866 Self::KillProcess => 3,
867 Self::KillThread => 4,
868 Self::Log => 5,
869 Self::Trace => 6,
870 Self::Trap(_) => 7,
871 Self::UserNotif => 8,
872 }
873 }
874
875 fn set_logged_bit(&self, dst: &mut u16) {
876 *dst |= 1 << self.logged_bit_offset();
877 }
878
879 pub fn is_logged(&self, kernel: &Kernel, filter_flag: bool) -> bool {
880 if kernel.actions_logged.load(Ordering::Relaxed) & (1 << self.logged_bit_offset()) != 0 {
881 match self {
882 Self::KillProcess | Self::KillThread => true,
888 Self::Errno(_) | Self::Log | Self::Trap(_) | Self::UserNotif => filter_flag,
890 _ => false,
892 }
893 } else {
894 false
895 }
896 }
897
898 pub fn set_actions_logged(kernel: &Kernel, data: &[u8]) -> Result<(), Errno> {
899 let mut new_actions_logged: u16 = 0;
900 for action_res in data.fields_with(|c| c.is_ascii_whitespace()) {
901 if let Ok(action) = action_res.to_str() {
902 match action {
903 "errno" => Self::Errno(0).set_logged_bit(&mut new_actions_logged),
904 "kill_process" => Self::KillProcess.set_logged_bit(&mut new_actions_logged),
905 "kill_thread" => Self::KillThread.set_logged_bit(&mut new_actions_logged),
906 "log" => Self::Log.set_logged_bit(&mut new_actions_logged),
907 "trace" => Self::Trace.set_logged_bit(&mut new_actions_logged),
908 "trap" => Self::Trap(0).set_logged_bit(&mut new_actions_logged),
909 "user_notif" => Self::UserNotif.set_logged_bit(&mut new_actions_logged),
910 _ => return error!(EINVAL),
912 }
913 } else {
914 return error!(EINVAL);
915 }
916 }
917 kernel.actions_logged.store(new_actions_logged, Ordering::Relaxed);
918 Ok(())
919 }
920
921 pub fn get_actions_logged(kernel: &Kernel) -> Vec<u8> {
922 let al = kernel.actions_logged.load(Ordering::Relaxed);
923 let mut result: String = "".to_string();
924 for action in Self::all_actions() {
925 if (al & (1 << action.logged_bit_offset())) != 0 {
926 result.push_str(action.canonical_name());
927 result.push(' ');
928 }
929 }
930 if !result.is_empty() {
931 result.pop();
933 }
934
935 result.into_bytes()
936 }
937}
938
939#[derive(Default)]
942struct SeccompNotification {
943 notif: seccomp_notif,
946
947 consumed: bool,
952
953 resp: Option<seccomp_notif_resp>,
957}
958
959impl SeccompNotification {
960 fn new(data: seccomp_notif) -> SeccompNotification {
961 SeccompNotification { notif: data, resp: None, consumed: false }
962 }
963}
964
965pub struct SeccompNotifier {
969 waiters: WaitQueue,
970
971 pending_notifications: HashMap<u64, SeccompNotification>,
972
973 num_active_threads: u64,
976
977 pub is_closed: bool,
981
982 next_cookie: u64,
983}
984
985pub type SeccompNotifierHandle = Arc<LockDepMutex<SeccompNotifier, SeccompNotifierLock>>;
986
987impl SeccompNotifier {
988 pub fn new() -> SeccompNotifierHandle {
989 Arc::new(
990 SeccompNotifier {
991 waiters: WaitQueue::default(),
992 pending_notifications: HashMap::default(),
993 num_active_threads: 0,
994 is_closed: false,
995 next_cookie: 0,
996 }
997 .into(),
998 )
999 }
1000
1001 fn next_cookie(&mut self) -> u64 {
1002 let cookie = self.next_cookie;
1003 self.next_cookie += 1;
1004 cookie
1005 }
1006
1007 fn add_thread(&mut self) {
1008 self.num_active_threads += 1;
1009 }
1010
1011 fn remove_thread(&mut self) {
1012 self.num_active_threads -= 1;
1013 if self.num_active_threads == 0 {
1014 self.waiters.notify_fd_events(FdEvents::POLLHUP);
1015 }
1016 }
1017
1018 fn create_notification(&mut self, cookie: u64, notif: seccomp_notif) {
1022 self.pending_notifications.insert(cookie, SeccompNotification::new(notif));
1023 self.waiters.notify_fd_events(FdEvents::POLLIN | FdEvents::POLLRDNORM);
1024 }
1025
1026 fn consume_some_notification(&mut self) -> Option<seccomp_notif> {
1029 for (_, notif) in self.pending_notifications.iter_mut() {
1030 if !notif.consumed {
1031 notif.consumed = true;
1032 self.waiters.notify_fd_events(FdEvents::POLLOUT | FdEvents::POLLWRNORM);
1033 return Some(notif.notif);
1034 }
1035 }
1036 None
1037 }
1038
1039 fn unconsume(&mut self, cookie: u64) {
1041 if let Some(n) = self.pending_notifications.get_mut(&cookie).as_mut() {
1042 n.consumed = false;
1043 }
1044 }
1045
1046 fn get_fd_notifications(&self) -> FdEvents {
1048 let mut events = FdEvents::empty();
1049
1050 for (_, notification) in self.pending_notifications.iter() {
1051 if !notification.consumed {
1052 events |= FdEvents::POLLIN | FdEvents::POLLRDNORM;
1053 } else if notification.resp.is_none() {
1054 events |= FdEvents::POLLOUT | FdEvents::POLLWRNORM;
1055 }
1056 }
1057
1058 if self.num_active_threads == 0 {
1059 events |= FdEvents::POLLHUP;
1060 }
1061 events
1062 }
1063
1064 fn set_response(&mut self, cookie: u64, resp: seccomp_notif_resp) -> Option<Errno> {
1067 if let Some(entry) = self.pending_notifications.get_mut(&cookie) {
1068 if entry.resp.is_some() {
1069 return Some(errno!(EINPROGRESS));
1070 }
1071 entry.resp = Some(resp);
1072 self.waiters.notify_value(resp.id);
1073 None
1074 } else {
1075 Some(errno!(EINVAL))
1076 }
1077 }
1078
1079 fn get_response(&self, cookie: u64) -> Option<seccomp_notif_resp> {
1081 if let Some(value) = self.pending_notifications.get(&cookie) {
1082 return value.resp;
1083 }
1084 None
1085 }
1086
1087 fn notification_pending(&self, cookie: u64) -> bool {
1089 self.pending_notifications.contains_key(&cookie)
1090 }
1091
1092 fn delete_notification(&mut self, cookie: u64) {
1094 let _ = self.pending_notifications.remove(&cookie);
1095 }
1096}
1097
1098struct SeccompNotifierFileObject {
1099 notifier: SeccompNotifierHandle,
1100}
1101
1102impl FileOps for SeccompNotifierFileObject {
1103 fileops_impl_nonseekable!();
1104 fileops_impl_noop_sync!();
1105
1106 fn close(self: Box<Self>, _file: &FileObjectState, _current_task: &CurrentTask) {
1107 let mut state = self.notifier.lock();
1108
1109 for (cookie, notification) in state.pending_notifications.iter() {
1110 if !notification.consumed {
1111 state.waiters.notify_value(*cookie);
1112 state.waiters.notify_fd_events(FdEvents::POLLIN | FdEvents::POLLRDNORM);
1113 } else if notification.resp.is_none() {
1114 state.waiters.notify_fd_events(FdEvents::POLLOUT | FdEvents::POLLWRNORM);
1115 }
1116 }
1117 state.waiters.notify_fd_events(FdEvents::POLLHUP);
1118
1119 state.pending_notifications.clear();
1120
1121 state.is_closed = true;
1122 }
1123
1124 fn read(
1125 &self,
1126 _file: &FileObject,
1127 _current_task: &CurrentTask,
1128 _offset: usize,
1129 _usize: &mut dyn OutputBuffer,
1130 ) -> Result<usize, Errno> {
1131 error!(EINVAL)
1132 }
1133
1134 fn write(
1135 &self,
1136 _file: &FileObject,
1137 _current_task: &CurrentTask,
1138 _offset: usize,
1139 _buffer: &mut dyn InputBuffer,
1140 ) -> Result<usize, Errno> {
1141 error!(EINVAL)
1142 }
1143
1144 fn ioctl(
1145 &self,
1146 _file: &FileObject,
1147 current_task: &CurrentTask,
1148 request: u32,
1149 arg: SyscallArg,
1150 ) -> Result<SyscallResult, Errno> {
1151 let user_addr = UserAddress::from(arg);
1152 match request {
1153 SECCOMP_IOCTL_NOTIF_RECV => {
1154 if let Ok(notif) =
1155 current_task.read_memory_to_vec(user_addr, std::mem::size_of::<seccomp_notif>())
1156 {
1157 for value in notif.iter() {
1158 if *value != 0 {
1159 return error!(EINVAL);
1160 }
1161 }
1162 }
1163 let mut notif: Option<seccomp_notif>;
1165 loop {
1166 let waiter = Waiter::new();
1168 {
1169 let mut notifier = self.notifier.lock();
1170 notif = notifier.consume_some_notification();
1171 if notif.is_some() {
1172 break;
1173 }
1174 notifier.waiters.wait_async_fd_events(
1175 &waiter,
1176 FdEvents::POLLIN | FdEvents::POLLHUP,
1177 EventHandler::None,
1178 );
1179 }
1180 waiter.wait(current_task)?;
1181 }
1182 if let Some(notif) = notif {
1183 if let Err(e) =
1184 current_task.write_object(UserRef::<seccomp_notif>::new(user_addr), ¬if)
1185 {
1186 self.notifier.lock().unconsume(notif.id);
1187 return Err(e);
1188 }
1189 }
1190
1191 Ok(0.into())
1192 }
1193 SECCOMP_IOCTL_NOTIF_SEND => {
1194 let resp: seccomp_notif_resp = current_task.read_object(UserRef::new(user_addr))?;
1196 if resp.flags & !SECCOMP_USER_NOTIF_FLAG_CONTINUE != 0 {
1197 return error!(EINVAL);
1198 }
1199 if resp.flags & SECCOMP_USER_NOTIF_FLAG_CONTINUE != 0
1200 && (resp.error != 0 || resp.val != 0)
1201 {
1202 return error!(EINVAL);
1203 }
1204 {
1205 let mut notifier = self.notifier.lock();
1206 if let Some(err) = notifier.set_response(resp.id, resp) {
1207 return Err(err);
1208 }
1209 }
1210 Ok(0.into())
1211 }
1212 SECCOMP_IOCTL_NOTIF_ID_VALID => {
1213 let cookie: u64 = current_task.read_object(UserRef::new(user_addr))?;
1215 {
1216 let notifier = self.notifier.lock();
1217 if notifier.notification_pending(cookie) {
1218 Ok(0.into())
1219 } else {
1220 error!(ENOENT)
1221 }
1222 }
1223 }
1224 SECCOMP_IOCTL_NOTIF_ADDFD => error!(EINVAL),
1225 _ => error!(EINVAL),
1226 }
1227 }
1228
1229 fn wait_async(
1230 &self,
1231 _file: &FileObject,
1232 _current_task: &CurrentTask,
1233 waiter: &Waiter,
1234 events: FdEvents,
1235 handler: EventHandler,
1236 ) -> Option<WaitCanceler> {
1237 let notifier = self.notifier.lock();
1238 Some(notifier.waiters.wait_async_fd_events(waiter, events, handler))
1239 }
1240
1241 fn query_events(
1242 &self,
1243 _file: &FileObject,
1244 _current_task: &CurrentTask,
1245 ) -> Result<FdEvents, Errno> {
1246 Ok(self.notifier.lock().get_fd_notifications())
1247 }
1248}
1249
1250#[cfg(test)]
1251mod test {
1252 use super::*;
1253 use crate::task::SeccompAction;
1254 use crate::testing::spawn_kernel_and_run;
1255 use linux_uapi::{SECCOMP_RET_ALLOW, SECCOMP_RET_ERRNO, SECCOMP_RET_KILL_PROCESS};
1256
1257 fn bpf_stmt(code: u8, k: u32) -> sock_filter {
1258 sock_filter { code: code as u16, jt: 0, jf: 0, k }
1259 }
1260
1261 fn bpf_jump(code: u8, k: u32, jt: u8, jf: u8) -> sock_filter {
1262 sock_filter { code: code as u16, jt, jf, k }
1263 }
1264
1265 #[::fuchsia::test]
1266 fn test_cbpf_always_allow_native_arch_and_nr() {
1267 let nr_offset = std::mem::offset_of!(seccomp_data, nr) as u32;
1268 let arch_offset = std::mem::offset_of!(seccomp_data, arch) as u32;
1269 let code = vec![
1270 bpf_stmt(BPF_LD | BPF_W | BPF_ABS, arch_offset),
1271 bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, NATIVE_AUDIT_ARCH, 0, 3),
1272 bpf_stmt(BPF_LD | BPF_W | BPF_ABS, nr_offset),
1273 bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 63, 1, 0),
1274 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
1275 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | 1),
1276 ];
1277
1278 assert!(cbpf_unconditionally_allows_native(&code, 64));
1279 assert!(!cbpf_unconditionally_allows_native(&code, 63));
1280
1281 let mask = compute_always_allow_mask(&code);
1282 assert_eq!(mask[1] & (1u64 << (64 % 64)), 1);
1283 assert_eq!(mask[0] & (1u64 << 63), 0);
1284 }
1285
1286 #[::fuchsia::test]
1287 fn test_cbpf_always_allow_jge_jgt_jset_ja_imm_ret_a() {
1288 let nr_offset = std::mem::offset_of!(seccomp_data, nr) as u32;
1289 let code = vec![
1294 bpf_stmt(BPF_LD | BPF_W | BPF_ABS, nr_offset),
1296 bpf_jump(BPF_JMP | BPF_JGE | BPF_K, 100, 1, 0),
1298 bpf_stmt(BPF_JMP | BPF_JA, 2),
1300 bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 200, 3, 0),
1302 bpf_jump(BPF_JMP | BPF_JSET | BPF_K, 1, 2, 0),
1304 bpf_stmt(BPF_LD | BPF_W | BPF_IMM, SECCOMP_RET_ALLOW),
1306 bpf_stmt(BPF_RET | BPF_A, 0),
1308 bpf_stmt(BPF_LD | BPF_W | BPF_IMM, SECCOMP_RET_ERRNO | 1),
1310 bpf_stmt(BPF_RET | BPF_A, 0),
1312 ];
1313
1314 assert!(cbpf_unconditionally_allows_native(&code, 0));
1315 assert!(cbpf_unconditionally_allows_native(&code, 99));
1316 assert!(cbpf_unconditionally_allows_native(&code, 100));
1317 assert!(!cbpf_unconditionally_allows_native(&code, 101));
1318 assert!(cbpf_unconditionally_allows_native(&code, 200));
1319 assert!(!cbpf_unconditionally_allows_native(&code, 201));
1320 }
1321
1322 #[::fuchsia::test]
1323 fn test_cbpf_always_allow_rejects_arg_inspection() {
1324 let nr_offset = std::mem::offset_of!(seccomp_data, nr) as u32;
1325 let arg0_offset = std::mem::offset_of!(seccomp_data, args) as u32;
1326 let code = vec![
1327 bpf_stmt(BPF_LD | BPF_W | BPF_ABS, nr_offset),
1328 bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 64, 0, 2),
1329 bpf_stmt(BPF_LD | BPF_W | BPF_ABS, arg0_offset),
1330 bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1),
1331 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
1332 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
1333 ];
1334
1335 assert!(!cbpf_unconditionally_allows_native(&code, 64));
1336 assert!(cbpf_unconditionally_allows_native(&code, 65));
1337 }
1338
1339 #[::fuchsia::test]
1340 fn test_seccomp_filter_container_intersections_and_state() {
1341 let nr_offset = std::mem::offset_of!(seccomp_data, nr) as u32;
1342 let filter1 = Arc::new(
1343 SeccompFilter::from_cbpf(
1344 &vec![
1345 bpf_stmt(BPF_LD | BPF_W | BPF_ABS, nr_offset),
1346 bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 1, 0),
1347 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
1348 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | 1),
1349 ],
1350 1,
1351 false,
1352 None,
1353 )
1354 .expect("filter1"),
1355 );
1356 let filter2 = Arc::new(
1357 SeccompFilter::from_cbpf(
1358 &vec![
1359 bpf_stmt(BPF_LD | BPF_W | BPF_ABS, nr_offset),
1360 bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 20, 1, 0),
1361 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
1362 bpf_stmt(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | 1),
1363 ],
1364 2,
1365 false,
1366 None,
1367 )
1368 .expect("filter2"),
1369 );
1370
1371 let mut container = SeccompFilterContainer::default();
1372 container.add_filter(filter1, 4).expect("add filter1");
1373 container.add_filter(filter2, 4).expect("add filter2");
1374
1375 let state = SeccompState::default();
1376 state.set_always_allow(&container.always_allow);
1377 state.set(&SeccompStateValue::UserDefined).expect("set UserDefined");
1378
1379 assert!(!state.is_syscall_always_allowed(10));
1380 assert!(!state.is_syscall_always_allowed(20));
1381 assert!(state.is_syscall_always_allowed(30));
1382 assert!(!state.is_syscall_always_allowed(NATIVE_ALWAYS_ALLOW_SYSCALLS as u64));
1383
1384 let strict_state = SeccompState::default();
1386 strict_state.set_always_allow(&[u64::MAX; NATIVE_ALWAYS_ALLOW_WORDS]);
1387 strict_state.set(&SeccompStateValue::Strict).expect("set Strict");
1388 assert!(!strict_state.is_syscall_always_allowed(30));
1389 }
1390
1391 #[::fuchsia::test]
1392 async fn test_actions_logged_accepts_legal_string() {
1393 spawn_kernel_and_run(async |current_task| {
1394 let kernel = current_task.kernel();
1395 let mut actions = SeccompAction::get_actions_avail_file();
1396 assert!(
1399 SeccompAction::set_actions_logged(&kernel, &actions[..]).is_err(),
1400 "Should not be able to write allow to actions_logged file"
1401 );
1402 let action_string = std::string::String::from_utf8(actions.clone()).unwrap();
1403 if let Some(action_index) = action_string.find("allow") {
1404 actions.drain(action_index..action_index + "allow".len());
1405 }
1406 let write_result = SeccompAction::set_actions_logged(&kernel, &actions[..]);
1407 assert!(
1408 write_result.is_ok(),
1409 "Could not write legal string \"{}\" to actions_logged file: error {}",
1410 std::string::String::from_utf8(actions.clone()).unwrap(),
1411 write_result.unwrap_err()
1412 );
1413 })
1414 .await;
1415 }
1416}