#![cfg(target_arch = "x86_64")]
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::time::Duration;
use std::time::Instant;
use reverie_process::Command;
use reverie_process::ExitStatus;
use reverie_process::launch_window;
const SECCOMP_IOCTL_NOTIF_RECV: libc::c_ulong = 0xc050_2100;
const SECCOMP_IOCTL_NOTIF_SEND: libc::c_ulong = 0xc018_2101;
const AUDIT_ARCH_X86_64: u32 = 0xc000_003e;
const BOUND: Duration = Duration::from_secs(30);
static ANSWERED_UNDER_LAUNCH: AtomicU32 = AtomicU32::new(0);
static STOP: AtomicBool = AtomicBool::new(false);
fn notify_pipe2_on_this_thread() -> libc::c_int {
let arch = std::mem::offset_of!(libc::seccomp_data, arch) as u32;
let nr = std::mem::offset_of!(libc::seccomp_data, nr) as u32;
let stmt = |code: u32, k: u32| libc::sock_filter {
code: code as u16,
jt: 0,
jf: 0,
k,
};
let jeq = |k: u32, jt: u8, jf: u8| libc::sock_filter {
code: (libc::BPF_JMP | libc::BPF_JEQ | libc::BPF_K) as u16,
jt,
jf,
k,
};
let filter = [
stmt(libc::BPF_LD | libc::BPF_W | libc::BPF_ABS, arch),
jeq(AUDIT_ARCH_X86_64, 0, 3),
stmt(libc::BPF_LD | libc::BPF_W | libc::BPF_ABS, nr),
jeq(libc::SYS_pipe2 as u32, 0, 1),
stmt(libc::BPF_RET | libc::BPF_K, libc::SECCOMP_RET_USER_NOTIF),
stmt(libc::BPF_RET | libc::BPF_K, libc::SECCOMP_RET_ALLOW),
];
let prog = libc::sock_fprog {
len: filter.len() as u16,
filter: filter.as_ptr() as *mut libc::sock_filter,
};
unsafe {
assert_eq!(libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0);
let listener = libc::syscall(
libc::SYS_seccomp,
libc::SECCOMP_SET_MODE_FILTER,
libc::SECCOMP_FILTER_FLAG_NEW_LISTENER,
&prog as *const libc::sock_fprog,
);
assert!(
listener >= 0,
"seccomp listener: {}",
std::io::Error::last_os_error()
);
listener as libc::c_int
}
}
fn supervise(listener: libc::c_int) {
while !STOP.load(Ordering::SeqCst) {
let mut ready = libc::pollfd {
fd: listener,
events: libc::POLLIN,
revents: 0,
};
if unsafe { libc::poll(&mut ready, 1, 50) } != 1 {
continue;
}
let mut request: libc::seccomp_notif = unsafe { std::mem::zeroed() };
if unsafe { libc::ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &mut request) } != 0 {
continue;
}
let under_launch = launch_window::launching();
launch_window::read_to_string("/proc/thread-self/status").unwrap();
if under_launch && request.data.nr == libc::SYS_pipe2 as i32 {
ANSWERED_UNDER_LAUNCH.fetch_add(1, Ordering::SeqCst);
}
let response = libc::seccomp_notif_resp {
id: request.id,
val: 0,
error: 0,
flags: libc::SECCOMP_USER_NOTIF_FLAG_CONTINUE as u32,
};
unsafe { libc::ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &response) };
}
}
#[test]
fn a_launch_whose_pipe_a_supervisor_here_answers_completes() {
let (listener_tx, listener_rx) = mpsc::channel();
let (done_tx, done_rx) = mpsc::channel();
std::thread::spawn(move || {
listener_tx.send(notify_pipe2_on_this_thread()).unwrap();
let began = Instant::now();
let status = Command::new("/bin/true")
.spawn()
.map_err(|e| e.to_string())
.and_then(|mut child| child.wait_blocking().map_err(|e| e.to_string()));
done_tx.send((status, began.elapsed())).unwrap();
});
let listener = listener_rx.recv().unwrap();
std::thread::spawn(move || supervise(listener));
let (status, took) = done_rx.recv_timeout(BOUND).unwrap_or_else(|_| {
panic!(
"the launch did not complete within {BOUND:?}: its pipe2 waits for the \
supervisor, whose transient open waits for the launch"
)
});
STOP.store(true, Ordering::SeqCst);
eprintln!(
"launch_supervised_syscall: spawn and wait took {took:?}; {} pipe2 notification(s) \
answered under the launch lock",
ANSWERED_UNDER_LAUNCH.load(Ordering::SeqCst)
);
assert_eq!(status, Ok(ExitStatus::Exited(0)));
assert!(
ANSWERED_UNDER_LAUNCH.load(Ordering::SeqCst) >= 1,
"no pipe2 was handed to the supervisor while the launch held the lock; the cycle was \
not exercised"
);
}