#![allow(unsafe_code)]
use std::ffi::{CStr, CString, c_int, c_long, c_ulong};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use super::seccomp::Insn;
pub(crate) fn errno() -> i32 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
fn check(rc: c_long) -> Result<c_long, i32> {
if rc < 0 { Err(errno()) } else { Ok(rc) }
}
fn check_int(rc: c_int) -> Result<(), i32> {
if rc < 0 { Err(errno()) } else { Ok(()) }
}
#[repr(C)]
struct RulesetAttr {
handled_access_fs: u64,
handled_access_net: u64,
scoped: u64,
}
#[repr(C, packed)]
struct PathBeneathAttr {
allowed_access: u64,
parent_fd: i32,
}
#[repr(C)]
struct NetPortAttr {
allowed_access: u64,
port: u64,
}
const CREATE_RULESET_VERSION: u32 = 1 << 0;
const CREATE_RULESET_ERRATA: u32 = 1 << 1;
const RULE_PATH_BENEATH: c_int = 1;
const RULE_NET_PORT: c_int = 2;
fn ruleset_query(flag: u32) -> Result<u32, i32> {
let rc = unsafe {
libc::syscall(
libc::SYS_landlock_create_ruleset,
std::ptr::null::<u8>(),
0usize,
flag,
)
};
check(rc).map(|v| u32::try_from(v).unwrap_or(0))
}
pub(crate) fn landlock_abi() -> Result<u32, i32> {
ruleset_query(CREATE_RULESET_VERSION)
}
pub(crate) fn landlock_errata() -> u32 {
ruleset_query(CREATE_RULESET_ERRATA).unwrap_or(0)
}
pub(crate) fn landlock_ruleset(fs: u64, net: u64, scoped: u64) -> Result<OwnedFd, i32> {
let attr = RulesetAttr {
handled_access_fs: fs,
handled_access_net: net,
scoped,
};
let rc = unsafe {
libc::syscall(
libc::SYS_landlock_create_ruleset,
&raw const attr,
std::mem::size_of::<RulesetAttr>(),
0u32,
)
};
let fd = c_int::try_from(check(rc)?).map_err(|_| libc::EBADF)?;
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
pub(crate) fn landlock_allow_path(
ruleset: &OwnedFd,
parent: &OwnedFd,
access: u64,
) -> Result<(), i32> {
let attr = PathBeneathAttr {
allowed_access: access,
parent_fd: parent.as_raw_fd(),
};
let rc = unsafe {
libc::syscall(
libc::SYS_landlock_add_rule,
ruleset.as_raw_fd(),
RULE_PATH_BENEATH,
&raw const attr,
0u32,
)
};
check(rc).map(drop)
}
pub(crate) fn landlock_allow_port(ruleset: &OwnedFd, port: u16, access: u64) -> Result<(), i32> {
let attr = NetPortAttr {
allowed_access: access,
port: u64::from(port),
};
let rc = unsafe {
libc::syscall(
libc::SYS_landlock_add_rule,
ruleset.as_raw_fd(),
RULE_NET_PORT,
&raw const attr,
0u32,
)
};
check(rc).map(drop)
}
pub(crate) fn open_path(path: &CStr) -> Result<(OwnedFd, bool), i32> {
let fd = unsafe { libc::open(path.as_ptr(), libc::O_PATH | libc::O_CLOEXEC) };
if fd < 0 {
return Err(errno());
}
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
let st = unsafe {
let mut st: libc::stat = std::mem::zeroed();
if libc::fstat(fd.as_raw_fd(), &raw mut st) != 0 {
return Err(errno());
}
st
};
Ok((fd, st.st_mode & libc::S_IFMT == libc::S_IFDIR))
}
const ST_RELATIME: c_ulong = 0x1000;
pub(crate) fn locked_flags(path: &CStr) -> Result<c_ulong, i32> {
let st = unsafe {
let mut st: libc::statvfs = std::mem::zeroed();
if libc::statvfs(path.as_ptr(), &raw mut st) != 0 {
return Err(errno());
}
st
};
let map = [
(libc::ST_RDONLY, libc::MS_RDONLY),
(libc::ST_NOSUID, libc::MS_NOSUID),
(libc::ST_NODEV, libc::MS_NODEV),
(libc::ST_NOEXEC, libc::MS_NOEXEC),
(libc::ST_NOATIME, libc::MS_NOATIME),
(libc::ST_NODIRATIME, libc::MS_NODIRATIME),
(ST_RELATIME, libc::MS_RELATIME),
];
Ok(map
.iter()
.filter(|(st_flag, _)| st.f_flag & st_flag != 0)
.fold(0, |acc, (_, ms)| acc | ms))
}
pub(crate) fn ids() -> (u32, u32) {
unsafe { (libc::getuid(), libc::getgid()) }
}
pub(crate) fn thread_count() -> usize {
std::fs::read_dir("/proc/self/task").map_or(0, Iterator::count)
}
pub(crate) fn pid() -> libc::pid_t {
unsafe { libc::getpid() }
}
pub(crate) fn parent_pid() -> libc::pid_t {
unsafe { libc::getppid() }
}
pub(crate) mod steps {
pub(crate) const SESSION: u32 = 1;
pub(crate) const LIFETIME: u32 = 2;
pub(crate) const NAMESPACES: u32 = 3;
pub(crate) const ID_MAP: u32 = 4;
pub(crate) const MOUNTS: u32 = 5;
pub(crate) const CAPABILITIES: u32 = 6;
pub(crate) const RLIMIT: u32 = 7;
pub(crate) const FDS: u32 = 8;
pub(crate) const NO_NEW_PRIVS: u32 = 9;
pub(crate) const LANDLOCK: u32 = 10;
pub(crate) const SECCOMP: u32 = 11;
pub(crate) const VERIFY: u32 = 12;
pub(crate) const LISTENER: u32 = 13;
}
const STEP_MARKER: i32 = 0x5043_0000;
pub(crate) const fn step_code(step: u32, errno: i32) -> i32 {
STEP_MARKER | ((step as i32 & 0xf) << 12) | (errno & 0xfff)
}
pub(crate) fn decode_step(code: i32) -> Option<(u32, i32)> {
((code as u32 & 0xffff_0000) == STEP_MARKER as u32)
.then_some((((code >> 12) & 0xf) as u32, code & 0xfff))
}
#[derive(Debug)]
pub(crate) enum ChildMount {
Bind {
source: CString,
target: CString,
recursive: bool,
optional: bool,
},
Remount { target: CString, flags: c_ulong },
Tmpfs {
target: CString,
flags: c_ulong,
data: CString,
optional: bool,
},
}
#[derive(Debug)]
pub(crate) struct Isolate {
pub(crate) flags: c_int,
pub(crate) uid_map: Vec<u8>,
pub(crate) gid_map: Vec<u8>,
pub(crate) mounts: Vec<ChildMount>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Session {
New,
Group,
Keep,
}
pub(crate) struct ChildPlan {
pub(crate) session: Session,
pub(crate) parent: libc::pid_t,
pub(crate) isolate: Option<Isolate>,
pub(crate) limits: Vec<(c_int, u64, u64)>,
pub(crate) keep: Vec<c_int>,
pub(crate) ruleset: OwnedFd,
pub(crate) filter: Vec<Insn>,
pub(crate) notify: Option<Notify>,
}
pub(crate) struct Notify {
pub(crate) filter: Vec<Insn>,
pub(crate) socket: OwnedFd,
pub(crate) control: Vec<u8>,
}
impl Notify {
pub(crate) fn new(filter: Vec<Insn>, socket: OwnedFd) -> Notify {
let space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<c_int>() as u32) } as usize;
Notify {
filter,
socket,
control: vec![0; space],
}
}
}
fn install_filter(filter: &mut [Insn], flags: libc::c_ulong) -> Result<c_int, i32> {
let prog = libc::sock_fprog {
len: u16::try_from(filter.len()).map_err(|_| libc::E2BIG)?,
filter: filter.as_mut_ptr().cast(),
};
let rc = unsafe {
libc::syscall(
libc::SYS_seccomp,
libc::SECCOMP_SET_MODE_FILTER,
flags,
&raw const prog,
)
};
if rc < 0 {
Err(errno())
} else {
Ok(c_int::try_from(rc).unwrap_or(-1))
}
}
fn send(socket: c_int, bytes: &[u8], fd: Option<c_int>, control: &mut [u8]) -> Result<(), i32> {
let mut iov = libc::iovec {
iov_base: bytes.as_ptr().cast_mut().cast(),
iov_len: bytes.len(),
};
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
msg.msg_iov = &raw mut iov;
msg.msg_iovlen = 1;
if let Some(fd) = fd {
msg.msg_control = control.as_mut_ptr().cast();
msg.msg_controllen = control.len() as _;
unsafe {
let header = libc::CMSG_FIRSTHDR(&raw const msg);
(*header).cmsg_level = libc::SOL_SOCKET;
(*header).cmsg_type = libc::SCM_RIGHTS;
(*header).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<c_int>() as u32) as _;
std::ptr::write_unaligned(libc::CMSG_DATA(header).cast::<c_int>(), fd);
}
}
let rc = unsafe { libc::sendmsg(socket, &raw const msg, libc::MSG_NOSIGNAL) };
if rc < 0 { Err(errno()) } else { Ok(()) }
}
fn write_file(path: &CStr, data: &[u8]) -> Result<(), i32> {
unsafe {
let fd = libc::open(path.as_ptr(), libc::O_WRONLY | libc::O_CLOEXEC);
if fd < 0 {
return Err(errno());
}
let n = libc::write(fd, data.as_ptr().cast(), data.len());
let e = errno();
libc::close(fd);
if usize::try_from(n).ok() != Some(data.len()) {
return Err(if n < 0 { e } else { libc::EIO });
}
}
Ok(())
}
fn mount(
source: Option<&CStr>,
target: &CStr,
fstype: Option<&CStr>,
flags: c_ulong,
data: Option<&CStr>,
) -> Result<(), i32> {
let p = |c: Option<&CStr>| c.map_or(std::ptr::null(), CStr::as_ptr);
let rc = unsafe { libc::mount(p(source), target.as_ptr(), p(fstype), flags, p(data).cast()) };
check_int(rc)
}
fn prctl(option: c_int, arg: c_ulong) -> Result<c_int, i32> {
let rc = unsafe { libc::prctl(option, arg, 0 as c_ulong, 0 as c_ulong, 0 as c_ulong) };
if rc < 0 { Err(errno()) } else { Ok(rc) }
}
#[repr(C)]
struct CapHeader {
version: u32,
pid: c_int,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct CapData {
effective: u32,
permitted: u32,
inheritable: u32,
}
const CAP_VERSION_3: u32 = 0x2008_0522;
fn drop_capabilities(bounding: bool) -> Result<(), i32> {
if bounding {
for cap in 0..64 {
match prctl(libc::PR_CAPBSET_DROP, cap) {
Ok(_) => {}
Err(libc::EINVAL) => break,
Err(e) => return Err(e),
}
}
}
match prctl(
libc::PR_CAP_AMBIENT,
libc::PR_CAP_AMBIENT_CLEAR_ALL as c_ulong,
) {
Ok(_) | Err(libc::EINVAL) => {}
Err(e) => return Err(e),
}
let header = CapHeader {
version: CAP_VERSION_3,
pid: 0,
};
let data = [CapData {
effective: 0,
permitted: 0,
inheritable: 0,
}; 2];
let rc = unsafe { libc::syscall(libc::SYS_capset, &raw const header, data.as_ptr()) };
check(rc).map(drop)
}
pub(crate) fn capabilities() -> (u64, u64, u64) {
let mut header = CapHeader {
version: CAP_VERSION_3,
pid: 0,
};
let mut data = [CapData {
effective: 0,
permitted: 0,
inheritable: 0,
}; 2];
unsafe { libc::syscall(libc::SYS_capget, &raw mut header, data.as_mut_ptr()) };
let join = |lo: u32, hi: u32| u64::from(lo) | (u64::from(hi) << 32);
let mut bounding = 0u64;
for cap in 0..64 {
match prctl(libc::PR_CAPBSET_READ, cap) {
Ok(1) => bounding |= 1 << cap,
Ok(_) => {}
Err(_) => break,
}
}
(
join(data[0].effective, data[1].effective),
join(data[0].permitted, data[1].permitted),
bounding,
)
}
fn close_range(first: u32, last: u32, flags: u32) -> c_long {
unsafe { libc::syscall(libc::SYS_close_range, first, last, flags) }
}
fn mark_fds(keep: &[c_int]) -> Result<(), i32> {
const CLOSE_RANGE_CLOEXEC: u32 = 1 << 2;
let mut first: u32 = 3;
let mut ranges_ok = true;
for &fd in keep.iter().chain(std::iter::once(&-1)) {
let last = if fd < 0 {
u32::MAX
} else {
match u32::try_from(fd) {
Ok(fd) if fd >= first => fd - 1,
Ok(fd) => {
first = first.max(fd + 1);
continue;
}
Err(_) => continue,
}
};
if last >= first && close_range(first, last, CLOSE_RANGE_CLOEXEC) != 0 {
ranges_ok = false;
break;
}
if fd >= 0 {
first = u32::try_from(fd).unwrap_or(u32::MAX).saturating_add(1);
}
}
if ranges_ok {
return Ok(());
}
#[allow(clippy::unnecessary_cast)]
let max = getrlimit(libc::RLIMIT_NOFILE as c_int).map_or(1024, |(soft, _)| soft.min(1 << 20));
for fd in 3..c_int::try_from(max).unwrap_or(c_int::MAX) {
if keep.contains(&fd) {
continue;
}
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } != 0 {
match errno() {
libc::EBADF => {}
e => return Err(e),
}
}
}
Ok(())
}
impl ChildPlan {
fn run(&mut self) -> std::io::Result<()> {
let fail =
|step: u32, errno: i32| std::io::Error::from_raw_os_error(step_code(step, errno));
let rc = unsafe {
match self.session {
Session::New => libc::setsid(),
Session::Group => libc::setpgid(0, 0),
Session::Keep => 0,
}
};
check_int(rc).map_err(|e| fail(steps::SESSION, e))?;
let parent = self.parent;
let death = move || -> std::io::Result<()> {
prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL as c_ulong)
.map_err(|e| fail(steps::LIFETIME, e))?;
if unsafe { libc::getppid() } != parent {
return Err(fail(steps::LIFETIME, libc::ESRCH));
}
Ok(())
};
death()?;
let t1 = self.isolate.is_some();
if let Some(iso) = &self.isolate {
check_int(unsafe { libc::unshare(iso.flags) })
.map_err(|e| fail(steps::NAMESPACES, e))?;
write_file(c"/proc/self/setgroups", b"deny")
.and_then(|()| write_file(c"/proc/self/uid_map", &iso.uid_map))
.and_then(|()| write_file(c"/proc/self/gid_map", &iso.gid_map))
.map_err(|e| fail(steps::ID_MAP, e))?;
mount(None, c"/", None, libc::MS_REC | libc::MS_PRIVATE, None)
.map_err(|e| fail(steps::MOUNTS, e))?;
let mut skipped: Option<&CStr> = None;
for m in &iso.mounts {
let (result, optional, target) = match m {
ChildMount::Bind {
source,
target,
recursive,
optional,
} => {
let rec = if *recursive { libc::MS_REC } else { 0 };
let r = mount(Some(source), target, None, libc::MS_BIND | rec, None);
(r, *optional, target.as_c_str())
}
ChildMount::Remount { target, flags } => {
if skipped == Some(target.as_c_str()) {
continue;
}
let r = mount(
None,
target,
None,
libc::MS_BIND | libc::MS_REMOUNT | flags,
None,
);
(r, false, target.as_c_str())
}
ChildMount::Tmpfs {
target,
flags,
data,
optional,
} => {
let r = mount(Some(c"tmpfs"), target, Some(c"tmpfs"), *flags, Some(data));
(r, *optional, target.as_c_str())
}
};
skipped = None;
match result {
Err(libc::ENOENT) if optional => skipped = Some(target),
r => r.map_err(|e| fail(steps::MOUNTS, e))?,
}
}
}
drop_capabilities(t1).map_err(|e| fail(steps::CAPABILITIES, e))?;
if t1 {
death()?;
}
for &(resource, soft, hard) in &self.limits {
let limit = libc::rlimit {
rlim_cur: soft,
rlim_max: hard,
};
#[allow(clippy::useless_conversion, clippy::unnecessary_cast)]
let rc = unsafe { libc::setrlimit(resource as _, &raw const limit) };
check_int(rc).map_err(|e| fail(steps::RLIMIT, e))?;
}
mark_fds(&self.keep).map_err(|e| fail(steps::FDS, e))?;
prctl(libc::PR_SET_NO_NEW_PRIVS, 1).map_err(|e| fail(steps::NO_NEW_PRIVS, e))?;
let rc = unsafe {
libc::syscall(
libc::SYS_landlock_restrict_self,
self.ruleset.as_raw_fd(),
0u32,
)
};
check(rc).map_err(|e| fail(steps::LANDLOCK, e))?;
match &mut self.notify {
Some(notify) => {
let socket = notify.socket.as_raw_fd();
match install_filter(&mut notify.filter, libc::SECCOMP_FILTER_FLAG_NEW_LISTENER) {
Ok(listener) => {
let sent = send(socket, &[1], Some(listener), &mut notify.control);
unsafe { libc::close(listener) };
sent.map_err(|e| fail(steps::LISTENER, e))?;
}
Err(refused) => {
install_filter(&mut self.filter, 0).map_err(|e| fail(steps::SECCOMP, e))?;
let mut status = [0u8; 5];
status[1..].copy_from_slice(&refused.to_le_bytes());
send(socket, &status, None, &mut notify.control)
.map_err(|e| fail(steps::LISTENER, e))?;
}
}
}
None => {
install_filter(&mut self.filter, 0).map_err(|e| fail(steps::SECCOMP, e))?;
}
}
let seccomp = prctl(libc::PR_GET_SECCOMP, 0).map_err(|e| fail(steps::VERIFY, e))?;
let nnp = prctl(libc::PR_GET_NO_NEW_PRIVS, 0).map_err(|e| fail(steps::VERIFY, e))?;
if seccomp != 2 || nnp != 1 {
return Err(fail(steps::VERIFY, libc::EPERM));
}
Ok(())
}
pub(crate) fn install(self, command: &mut std::process::Command) {
use std::os::unix::process::CommandExt;
let mut plan = self;
unsafe {
command.pre_exec(move || plan.run());
}
}
}
pub(crate) fn probe_userns(flags: c_int) -> Result<(), i32> {
let (uid, gid) = ids();
let uid_map = format!("{uid} {uid} 1\n").into_bytes();
let gid_map = format!("{gid} {gid} 1\n").into_bytes();
let mut fds = [0 as c_int; 2];
check_int(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) })?;
let (read, write) = unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) };
let pid = unsafe { libc::fork() };
if pid < 0 {
return Err(errno());
}
if pid == 0 {
let result = (|| {
check_int(unsafe { libc::unshare(flags) })?;
write_file(c"/proc/self/setgroups", b"deny")?;
write_file(c"/proc/self/uid_map", &uid_map)?;
write_file(c"/proc/self/gid_map", &gid_map)?;
mount(None, c"/", None, libc::MS_REC | libc::MS_PRIVATE, None)?;
mount(Some(c"tmpfs"), c"/tmp", Some(c"tmpfs"), 0, Some(c""))
})();
let code: i32 = result.err().unwrap_or(0);
unsafe {
libc::write(write.as_raw_fd(), (&raw const code).cast(), 4);
libc::_exit(0);
}
}
drop(write);
let mut code: i32 = libc::EIO;
unsafe {
libc::read(read.as_raw_fd(), (&raw mut code).cast(), 4);
let mut status = 0;
libc::waitpid(pid, &raw mut status, 0);
}
if code == 0 { Ok(()) } else { Err(code) }
}
pub(crate) fn kill(pid: libc::pid_t) {
unsafe { libc::kill(pid, libc::SIGKILL) };
}
pub(crate) fn killpg(pgid: libc::pid_t) {
unsafe { libc::killpg(pgid, libc::SIGKILL) };
}
pub(crate) fn alive(pid: libc::pid_t) -> bool {
unsafe { libc::kill(pid, 0) == 0 || errno() == libc::EPERM }
}
#[allow(clippy::useless_conversion, clippy::unnecessary_cast)]
pub(crate) fn getrlimit(resource: c_int) -> Option<(u64, u64)> {
let mut limit = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
(unsafe { libc::getrlimit(resource as _, &raw mut limit) } == 0)
.then_some((limit.rlim_cur, limit.rlim_max))
}
pub(crate) fn fd_is_open(fd: c_int) -> bool {
unsafe { libc::fcntl(fd, libc::F_GETFD) != -1 }
}
pub(crate) fn clear_cloexec(fd: c_int) -> Result<(), i32> {
check_int(unsafe { libc::fcntl(fd, libc::F_SETFD, 0) })
}
pub(crate) fn leadership() -> (bool, bool) {
unsafe {
let me = libc::getpid();
(libc::getsid(0) == me, libc::getpgrp() == me)
}
}
pub(crate) fn syscall_errno(nr: c_long, args: [c_long; 5]) -> i32 {
let rc = unsafe { libc::syscall(nr, args[0], args[1], args[2], args[3], args[4]) };
if rc < 0 { errno() } else { 0 }
}
pub(crate) fn spawn_sleeper(setsid: bool) -> std::io::Result<u32> {
use std::os::unix::process::CommandExt;
let mut cmd = std::process::Command::new("/bin/sleep");
cmd.arg("30");
if setsid {
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
}
let child = cmd.spawn()?;
Ok(child.id())
}
pub(crate) fn open_pty() -> std::io::Result<(OwnedFd, std::path::PathBuf)> {
use std::os::unix::ffi::OsStrExt;
let (mut controller, mut device) = (-1, -1);
let mut name = [0 as libc::c_char; 128];
let rc = unsafe {
libc::openpty(
&raw mut controller,
&raw mut device,
name.as_mut_ptr(),
std::ptr::null(),
std::ptr::null(),
)
};
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
let controller = unsafe {
drop(OwnedFd::from_raw_fd(device));
OwnedFd::from_raw_fd(controller)
};
let name = unsafe { CStr::from_ptr(name.as_ptr()) };
Ok((
controller,
std::ffi::OsStr::from_bytes(name.to_bytes()).into(),
))
}
pub(crate) fn tty_ioctl_errno(fd: c_int, byte: Option<u8>) -> i32 {
let c = byte.unwrap_or(0) as libc::c_char;
#[allow(clippy::useless_conversion, clippy::unnecessary_cast)]
let rc = unsafe {
match byte {
None => libc::ioctl(fd, libc::TIOCSCTTY as _, 0),
Some(_) => libc::ioctl(fd, libc::TIOCSTI as _, &raw const c),
}
};
if rc == -1 { errno() } else { 0 }
}
pub(crate) fn install_outer_listener(mut filter: Vec<Insn>) -> Result<OwnedFd, i32> {
prctl(libc::PR_SET_NO_NEW_PRIVS, 1)?;
let fd = install_filter(&mut filter, libc::SECCOMP_FILTER_FLAG_NEW_LISTENER)?;
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
pub(crate) fn socket_pair() -> std::io::Result<(OwnedFd, OwnedFd)> {
let mut fds = [0 as c_int; 2];
let rc = unsafe {
libc::socketpair(
libc::AF_UNIX,
libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC,
0,
fds.as_mut_ptr(),
)
};
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
}
pub(crate) enum HandOver {
Listener(OwnedFd),
Refused(i32),
}
pub(crate) fn receive(socket: &OwnedFd) -> Result<HandOver, i32> {
let mut bytes = [0u8; 5];
let mut iov = libc::iovec {
iov_base: bytes.as_mut_ptr().cast(),
iov_len: bytes.len(),
};
let space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<c_int>() as u32) } as usize;
let mut control = vec![0u8; space];
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
msg.msg_iov = &raw mut iov;
msg.msg_iovlen = 1;
msg.msg_control = control.as_mut_ptr().cast();
msg.msg_controllen = control.len() as _;
let n = unsafe {
libc::recvmsg(
socket.as_raw_fd(),
&raw mut msg,
libc::MSG_DONTWAIT | libc::MSG_CMSG_CLOEXEC,
)
};
if n < 1 {
return Err(if n < 0 { errno() } else { libc::EPIPE });
}
let header = unsafe { libc::CMSG_FIRSTHDR(&raw const msg) };
let fd = if header.is_null() {
None
} else {
let h = unsafe { &*header };
(h.cmsg_level == libc::SOL_SOCKET && h.cmsg_type == libc::SCM_RIGHTS).then(|| {
unsafe { std::ptr::read_unaligned(libc::CMSG_DATA(header).cast::<c_int>()) }
})
};
let fd = fd.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) });
match (bytes[0], fd) {
(1, Some(fd)) => Ok(HandOver::Listener(fd)),
(0, None) if n == 5 => Ok(HandOver::Refused(i32::from_le_bytes([
bytes[1], bytes[2], bytes[3], bytes[4],
]))),
_ => Err(libc::EPROTO),
}
}
pub(crate) enum Wait {
Ready,
HungUp,
Idle,
}
pub(crate) fn wait_listener(listener: &OwnedFd, timeout_ms: c_int) -> Result<Wait, i32> {
let mut pfd = libc::pollfd {
fd: listener.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let rc = unsafe { libc::poll(&raw mut pfd, 1, timeout_ms) };
if rc < 0 {
let e = errno();
return if e == libc::EINTR {
Ok(Wait::Idle)
} else {
Err(e)
};
}
if rc == 0 {
return Ok(Wait::Idle);
}
if pfd.revents & libc::POLLIN != 0 {
Ok(Wait::Ready)
} else {
Ok(Wait::HungUp)
}
}
pub(crate) struct Notification {
pub(crate) id: u64,
pub(crate) pid: u32,
pub(crate) nr: u32,
pub(crate) args: [u64; 6],
}
pub(crate) fn receive_notification(listener: &OwnedFd) -> Result<Notification, i32> {
let mut n: libc::seccomp_notif = unsafe { std::mem::zeroed() };
let rc = unsafe {
libc::ioctl(
listener.as_raw_fd(),
libc::SECCOMP_IOCTL_NOTIF_RECV,
&raw mut n,
)
};
if rc != 0 {
return Err(errno());
}
Ok(Notification {
id: n.id,
pid: n.pid,
nr: n.data.nr as u32,
args: n.data.args,
})
}
pub(crate) fn answer_eperm(listener: &OwnedFd, id: u64) -> Result<(), i32> {
let mut resp = libc::seccomp_notif_resp {
id,
val: 0,
error: -libc::EPERM,
flags: 0,
};
let rc = unsafe {
libc::ioctl(
listener.as_raw_fd(),
libc::SECCOMP_IOCTL_NOTIF_SEND,
&raw mut resp,
)
};
if rc != 0 { Err(errno()) } else { Ok(()) }
}
pub(crate) fn notification_valid(listener: &OwnedFd, id: u64) -> bool {
unsafe {
libc::ioctl(
listener.as_raw_fd(),
libc::SECCOMP_IOCTL_NOTIF_ID_VALID,
&raw const id,
) == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insn_has_the_layout_of_sock_filter() {
assert_eq!(
std::mem::size_of::<Insn>(),
std::mem::size_of::<libc::sock_filter>()
);
assert_eq!(
std::mem::align_of::<Insn>(),
std::mem::align_of::<libc::sock_filter>()
);
}
#[test]
fn step_codes_round_trip() {
assert_eq!(decode_step(step_code(steps::VERIFY, 13)), Some((12, 13)));
assert_eq!(decode_step(13), None);
}
}