use crate::Result;
const BPF_LD: u16 = 0x00;
const BPF_JMP: u16 = 0x05;
const BPF_RET: u16 = 0x06;
const BPF_W: u16 = 0x00;
const BPF_ABS: u16 = 0x20;
const BPF_JEQ: u16 = 0x10;
const BPF_K: u16 = 0x00;
const SECCOMP_RET_ERRNO: u32 = 0x0005_0000;
const SECCOMP_RET_ALLOW: u32 = 0x7fff_0000;
const SECCOMP_RET_DATA: u32 = 0x0000_ffff;
const SECCOMP_SET_MODE_FILTER: libc::c_ulong = 1;
const AUDIT_ARCH_X86_64: u32 = 0xC000_003E;
const TIOCSTI: u32 = 0x5412;
const OFFSET_NR: u32 = 0;
const OFFSET_ARCH: u32 = 4;
const OFFSET_ARG1: u32 = 24;
#[repr(C)]
struct SockFilter {
code: u16,
jt: u8,
jf: u8,
k: u32,
}
#[repr(C)]
struct SockFprog {
len: u16,
filter: *const SockFilter,
}
const fn stmt(code: u16, k: u32) -> SockFilter {
SockFilter {
code,
jt: 0,
jf: 0,
k,
}
}
const fn jump(code: u16, k: u32, jt: u8, jf: u8) -> SockFilter {
SockFilter { code, jt, jf, k }
}
pub fn install_tiocsti_block() -> Result<()> {
let filter = [
stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARCH),
jump(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 5),
stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR),
jump(BPF_JMP | BPF_JEQ | BPF_K, libc::SYS_ioctl as u32, 0, 3),
stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARG1),
jump(BPF_JMP | BPF_JEQ | BPF_K, TIOCSTI, 0, 1),
stmt(
BPF_RET | BPF_K,
SECCOMP_RET_ERRNO | (libc::EPERM as u32 & SECCOMP_RET_DATA),
),
stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
];
let prog = SockFprog {
len: filter.len() as u16,
filter: filter.as_ptr(),
};
let rc = unsafe {
libc::syscall(
libc::SYS_seccomp,
SECCOMP_SET_MODE_FILTER,
0,
&prog as *const SockFprog,
)
};
if rc != 0 {
return Err(format!(
"failed to install seccomp TIOCSTI filter: {}",
std::io::Error::last_os_error()
)
.into());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blocks_tiocsti_but_allows_other_ioctls() {
unsafe {
let mut fds = [0i32; 2];
assert_eq!(libc::pipe(fds.as_mut_ptr()), 0, "pipe failed");
let pid = libc::fork();
assert!(pid >= 0, "fork failed");
if pid == 0 {
if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 {
libc::_exit(10);
}
if install_tiocsti_block().is_err() {
libc::_exit(11);
}
let ch: u8 = b'x';
let r = libc::ioctl(fds[0], TIOCSTI as _, &ch as *const u8);
if r != -1 || *libc::__errno_location() != libc::EPERM {
libc::_exit(12);
}
let mut navail: libc::c_int = -1;
if libc::ioctl(fds[0], libc::FIONREAD as _, &mut navail) != 0 {
libc::_exit(13);
}
libc::_exit(0);
}
libc::close(fds[0]);
libc::close(fds[1]);
let mut status = 0i32;
assert_eq!(libc::waitpid(pid, &mut status, 0), pid, "waitpid failed");
assert!(libc::WIFEXITED(status), "child did not exit normally");
assert_eq!(
libc::WEXITSTATUS(status),
0,
"child failed at stage {}",
libc::WEXITSTATUS(status)
);
}
}
}