use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::sync::Arc;
use crate::seccomp::ctx::SupervisorCtx;
use crate::seccomp::notif::{read_child_mem, NotifAction};
use crate::sys::structs::{SeccompNotif, ECONNREFUSED};
use super::materialize::{
materialize_msg, mmsg_entry_ptr, mmsg_msglen_addr, named_unix_socket_path, ChildMsghdr,
MaterializedMsg,
};
use super::send_engine::{batch_send_step, resolve_send, wants_blocking, BatchStep};
use super::verdict::{path_under_any, real_path_under_any};
fn resolve_named_unix_target(
child_pid: u32,
sun_path: &std::path::Path,
writable: &[std::path::PathBuf],
) -> Result<OwnedFd, NotifAction> {
let proc_path = format!("/proc/{}/root{}", child_pid, sun_path.display());
let c_proc = std::ffi::CString::new(proc_path)
.map_err(|_| NotifAction::Errno(libc::EACCES))?;
let pinned_raw = unsafe { libc::open(c_proc.as_ptr(), libc::O_PATH | libc::O_CLOEXEC) };
if pinned_raw < 0 {
return Err(NotifAction::Errno(ECONNREFUSED));
}
let pinned = unsafe { OwnedFd::from_raw_fd(pinned_raw) };
let real = std::fs::read_link(format!("/proc/self/fd/{}", pinned.as_raw_fd()))
.map_err(|_| NotifAction::Errno(libc::EACCES))?;
if real_path_under_any(&real, writable) {
Ok(pinned)
} else {
Err(NotifAction::Errno(libc::EACCES))
}
}
fn proc_self_fd_sockaddr(fd: RawFd) -> Option<(libc::sockaddr_un, libc::socklen_t)> {
let path = format!("/proc/self/fd/{}", fd);
let bytes = path.as_bytes();
let mut sun: libc::sockaddr_un = unsafe { std::mem::zeroed() };
if bytes.len() >= sun.sun_path.len() {
return None;
}
sun.sun_family = libc::AF_UNIX as libc::sa_family_t;
for (i, &b) in bytes.iter().enumerate() {
sun.sun_path[i] = b as libc::c_char;
}
let len = (std::mem::size_of::<libc::sa_family_t>() + bytes.len() + 1) as libc::socklen_t;
Some((sun, len))
}
pub(super) fn connect_named_unix_on_behalf(
child_pid: u32,
sockfd: i32,
sun_path: &std::path::Path,
writable: &[std::path::PathBuf],
) -> NotifAction {
let pinned = match resolve_named_unix_target(child_pid, sun_path, writable) {
Ok(fd) => fd,
Err(action) => return action,
};
let (sun, len) = match proc_self_fd_sockaddr(pinned.as_raw_fd()) {
Some(s) => s,
None => return NotifAction::Errno(libc::ENAMETOOLONG),
};
let dup_fd = match crate::seccomp::notif::dup_fd_from_pid(child_pid, sockfd) {
Ok(fd) => fd,
Err(e) => return NotifAction::Errno(e.raw_os_error().unwrap_or(libc::EBADF)),
};
let ret = unsafe {
libc::connect(
dup_fd.as_raw_fd(),
&sun as *const libc::sockaddr_un as *const libc::sockaddr,
len,
)
};
if ret == 0 {
NotifAction::ReturnValue(0)
} else {
NotifAction::Errno(unsafe { *libc::__errno_location() })
}
}
pub(super) fn sendto_named_unix_on_behalf(
notif: &SeccompNotif,
notif_fd: RawFd,
sockfd: i32,
buf_ptr: u64,
buf_len: usize,
flags: i32,
sun_path: &std::path::Path,
writable: &[std::path::PathBuf],
) -> NotifAction {
let pinned = match resolve_named_unix_target(notif.pid, sun_path, writable) {
Ok(fd) => fd,
Err(action) => return action,
};
let (sun, len) = match proc_self_fd_sockaddr(pinned.as_raw_fd()) {
Some(s) => s,
None => return NotifAction::Errno(libc::ENAMETOOLONG),
};
let data = match read_child_mem(notif_fd, notif.id, notif.pid, buf_ptr, buf_len) {
Ok(b) => b,
Err(_) => return NotifAction::Errno(libc::EIO),
};
let dup_fd = match crate::seccomp::notif::dup_fd_from_pid(notif.pid, sockfd) {
Ok(fd) => fd,
Err(e) => return NotifAction::Errno(e.raw_os_error().unwrap_or(libc::EBADF)),
};
let addr = unsafe {
std::slice::from_raw_parts(&sun as *const libc::sockaddr_un as *const u8, len as usize)
}
.to_vec();
let m = MaterializedMsg {
data,
control: None,
addr,
_scm_fds: Vec::new(),
_pinned: Some(pinned),
};
let blocking = wants_blocking(dup_fd.as_raw_fd(), flags);
resolve_send(dup_fd, m, flags, blocking)
}
pub(super) fn unix_sendmsg_gate(
notif: &SeccompNotif,
ctx: &Arc<SupervisorCtx>,
notif_fd: RawFd,
sockfd: i32,
msghdr_ptr: u64,
flags: i32,
) -> Option<NotifAction> {
let hdr = ChildMsghdr::read(notif, notif_fd, msghdr_ptr).ok()?;
if hdr.connected() {
return None; }
let addr_bytes =
super::read_sockaddr(notif_fd, notif.id, notif.pid, hdr.name_ptr, hdr.namelen as usize).ok()?;
let path = named_unix_socket_path(&addr_bytes)?;
if ctx.policy.chroot_root.is_some() {
return Some(if path_under_any(&path, &ctx.policy.chroot_writable) {
NotifAction::Continue
} else {
NotifAction::Errno(libc::EACCES)
});
}
Some(sendmsg_named_unix_on_behalf(
notif,
notif_fd,
sockfd,
msghdr_ptr,
flags,
&path,
&ctx.policy.chroot_writable,
))
}
fn sendmsg_named_unix_on_behalf(
notif: &SeccompNotif,
notif_fd: RawFd,
sockfd: i32,
msghdr_ptr: u64,
flags: i32,
sun_path: &std::path::Path,
writable: &[std::path::PathBuf],
) -> NotifAction {
match send_named_unix_msghdr(notif, notif_fd, sockfd, msghdr_ptr, sun_path, writable) {
Ok((dup_fd, m)) => {
let blocking = wants_blocking(dup_fd.as_raw_fd(), flags);
resolve_send(dup_fd, m, flags, blocking)
}
Err(errno) => NotifAction::Errno(errno),
}
}
fn send_named_unix_msghdr(
notif: &SeccompNotif,
notif_fd: RawFd,
sockfd: i32,
msghdr_ptr: u64,
sun_path: &std::path::Path,
writable: &[std::path::PathBuf],
) -> Result<(OwnedFd, MaterializedMsg), i32> {
let pinned = match resolve_named_unix_target(notif.pid, sun_path, writable) {
Ok(fd) => fd,
Err(NotifAction::Errno(e)) => return Err(e),
Err(_) => return Err(libc::EACCES),
};
let (sun, sun_len) = proc_self_fd_sockaddr(pinned.as_raw_fd()).ok_or(libc::ENAMETOOLONG)?;
let hdr = ChildMsghdr::read(notif, notif_fd, msghdr_ptr)?;
let addr = unsafe {
std::slice::from_raw_parts(&sun as *const libc::sockaddr_un as *const u8, sun_len as usize)
}
.to_vec();
let m = materialize_msg(notif, notif_fd, &hdr, addr, true, Some(pinned))?;
let dup_fd = crate::seccomp::notif::dup_fd_from_pid(notif.pid, sockfd)
.map_err(|e| e.raw_os_error().unwrap_or(libc::EBADF))?;
Ok((dup_fd, m))
}
pub(super) fn mmsg_entry_named_unix_path(
notif: &SeccompNotif,
notif_fd: RawFd,
entry_ptr: u64,
) -> Option<std::path::PathBuf> {
let hdr = ChildMsghdr::read(notif, notif_fd, entry_ptr).ok()?;
if hdr.connected() {
return None;
}
let addr_bytes =
super::read_sockaddr(notif_fd, notif.id, notif.pid, hdr.name_ptr, hdr.namelen as usize).ok()?;
named_unix_socket_path(&addr_bytes)
}
pub(super) fn sendmmsg_named_unix_on_behalf(
notif: &SeccompNotif,
notif_fd: RawFd,
sockfd: i32,
msgvec_ptr: u64,
vlen: usize,
flags: i32,
writable: &[std::path::PathBuf],
) -> NotifAction {
let mut sent: usize = 0;
let mut first_errno: Option<i32> = None;
for i in 0..vlen {
let entry_ptr = mmsg_entry_ptr(msgvec_ptr, i);
let path = match mmsg_entry_named_unix_path(notif, notif_fd, entry_ptr) {
Some(p) => p,
None => break,
};
let (dup_fd, m) = match send_named_unix_msghdr(notif, notif_fd, sockfd, entry_ptr, &path, writable) {
Ok(pair) => pair,
Err(errno) => {
first_errno = Some(errno);
break;
}
};
match batch_send_step(
&dup_fd, m, flags, notif_fd, notif.id, notif.pid,
mmsg_msglen_addr(entry_ptr), sent,
) {
BatchStep::Sent => sent += 1,
BatchStep::Done(action) => return action,
BatchStep::Stop(errno) => {
if sent == 0 {
first_errno = Some(errno);
}
break;
}
}
}
if sent > 0 {
NotifAction::ReturnValue(sent as i64)
} else {
NotifAction::Errno(first_errno.unwrap_or(libc::EACCES))
}
}