use std::os::unix::io::{AsRawFd, 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,
parse_ip_from_sockaddr, parse_port_from_sockaddr, ChildMsghdr, MaterializedMsg,
MAX_SEND_BUF,
};
use super::send_engine::{batch_send_step, resolve_send, wants_blocking, BatchStep};
use super::unix::{
mmsg_entry_named_unix_path, sendmmsg_named_unix_on_behalf, sendto_named_unix_on_behalf,
unix_sendmsg_gate,
};
use super::verdict::{check_ip_destination, path_under_any};
use super::{query_socket_protocol, socket_is_unix, Protocol};
pub(super) async fn sendto_on_behalf(
notif: &SeccompNotif,
ctx: &Arc<SupervisorCtx>,
notif_fd: RawFd,
) -> NotifAction {
let args = ¬if.data.args;
let sockfd = args[0] as i32;
let buf_ptr = args[1];
let buf_len = args[2] as usize;
if buf_len > MAX_SEND_BUF {
return NotifAction::Errno(libc::EMSGSIZE);
}
let flags = args[3] as i32;
let addr_ptr = args[4];
let addr_len = args[5] as u32;
if addr_ptr == 0 {
return NotifAction::Continue; }
let addr_bytes =
match super::read_sockaddr(notif_fd, notif.id, notif.pid, addr_ptr, addr_len as usize) {
Ok(b) => b,
Err(e) => return NotifAction::Errno(e),
};
if let Some(ip) = parse_ip_from_sockaddr(&addr_bytes) {
let dest_port = parse_port_from_sockaddr(&addr_bytes);
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 protocol = match query_socket_protocol(dup_fd.as_raw_fd()) {
Some(p) => p,
None => return NotifAction::Errno(ECONNREFUSED),
};
if let Err(e) = check_ip_destination(ctx, notif.pid, protocol, ip, dest_port).await {
return NotifAction::Errno(e);
}
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 m = MaterializedMsg {
data,
control: None,
addr: addr_bytes,
_scm_fds: Vec::new(),
_pinned: None,
};
let blocking = wants_blocking(dup_fd.as_raw_fd(), flags);
resolve_send(dup_fd, m, flags, blocking)
} else {
match named_unix_socket_path(&addr_bytes) {
Some(path) if ctx.policy.has_unix_fs_gate => {
if ctx.policy.chroot_root.is_some() {
if path_under_any(&path, &ctx.policy.chroot_writable) {
NotifAction::Continue
} else {
NotifAction::Errno(libc::EACCES)
}
} else {
sendto_named_unix_on_behalf(
notif,
notif_fd,
sockfd,
buf_ptr,
buf_len,
flags,
&path,
&ctx.policy.chroot_writable,
)
}
}
_ => NotifAction::Continue,
}
}
}
pub(super) async fn sendmsg_on_behalf(
notif: &SeccompNotif,
ctx: &Arc<SupervisorCtx>,
notif_fd: RawFd,
) -> NotifAction {
let args = ¬if.data.args;
let sockfd = args[0] as i32;
let msghdr_ptr = args[1];
let flags = args[2] as i32;
if ctx.policy.has_unix_fs_gate {
if let Some(action) = unix_sendmsg_gate(notif, ctx, notif_fd, sockfd, msghdr_ptr, flags) {
return action;
}
}
let dest_policy = ctx.policy.has_net_destination_policy;
if !dest_policy {
match prescan_msghdr(notif, notif_fd, msghdr_ptr) {
PrescanResult::ContinueWholeCall => return NotifAction::Continue,
PrescanResult::Errno(e) => return NotifAction::Errno(e),
PrescanResult::OnBehalf => {}
}
}
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 protocol = query_socket_protocol(dup_fd.as_raw_fd());
match send_msghdr_on_behalf(notif, ctx, notif_fd, &dup_fd, protocol, msghdr_ptr).await {
Ok(m) => {
let blocking = wants_blocking(dup_fd.as_raw_fd(), flags);
resolve_send(dup_fd, m, flags, blocking)
}
Err(errno) => NotifAction::Errno(errno),
}
}
#[derive(Clone, Copy)]
enum PrescanResult {
OnBehalf,
ContinueWholeCall,
Errno(i32),
}
fn prescan_msghdr(
notif: &SeccompNotif,
notif_fd: RawFd,
msghdr_ptr: u64,
) -> PrescanResult {
let hdr = match ChildMsghdr::read(notif, notif_fd, msghdr_ptr) {
Ok(h) => h,
Err(e) => return PrescanResult::Errno(e),
};
if hdr.connected() {
return PrescanResult::ContinueWholeCall;
}
let addr_bytes = match super::read_sockaddr(notif_fd, notif.id, notif.pid, hdr.name_ptr, hdr.namelen as usize) {
Ok(b) => b,
Err(e) => return PrescanResult::Errno(e),
};
if parse_ip_from_sockaddr(&addr_bytes).is_none() {
return PrescanResult::ContinueWholeCall;
}
PrescanResult::OnBehalf
}
async fn send_msghdr_on_behalf(
notif: &SeccompNotif,
ctx: &Arc<SupervisorCtx>,
notif_fd: RawFd,
dup_fd: &std::os::unix::io::OwnedFd,
protocol: Option<Protocol>,
msghdr_ptr: u64,
) -> Result<MaterializedMsg, i32> {
let hdr = ChildMsghdr::read(notif, notif_fd, msghdr_ptr)?;
let connected = hdr.connected();
let addr_bytes = if connected {
Vec::new()
} else {
match super::read_sockaddr(notif_fd, notif.id, notif.pid, hdr.name_ptr, hdr.namelen as usize) {
Ok(b) => b,
Err(e) => return Err(e),
}
};
if !connected {
let ip = match parse_ip_from_sockaddr(&addr_bytes) {
Some(ip) => ip,
None => return Err(libc::EAFNOSUPPORT),
};
let dest_port = parse_port_from_sockaddr(&addr_bytes);
let protocol = protocol.ok_or(ECONNREFUSED)?;
check_ip_destination(ctx, notif.pid, protocol, ip, dest_port).await?;
}
materialize_msg(
notif,
notif_fd,
&hdr,
addr_bytes,
socket_is_unix(dup_fd.as_raw_fd()),
None,
)
}
const MAX_MMSGHDR_ENTRIES: usize = 256;
pub(super) async fn sendmmsg_on_behalf(
notif: &SeccompNotif,
ctx: &Arc<SupervisorCtx>,
notif_fd: RawFd,
) -> NotifAction {
let args = ¬if.data.args;
let sockfd = args[0] as i32;
let msgvec_ptr = args[1];
let vlen = (args[2] as u32 as usize).min(MAX_MMSGHDR_ENTRIES);
let flags = args[3] as i32;
if vlen == 0 {
return NotifAction::ReturnValue(0);
}
if ctx.policy.has_unix_fs_gate {
let mut named_unix = false;
for i in 0..vlen {
let entry_ptr = mmsg_entry_ptr(msgvec_ptr, i);
if mmsg_entry_named_unix_path(notif, notif_fd, entry_ptr).is_some() {
named_unix = true;
break;
}
}
if named_unix {
if ctx.policy.chroot_root.is_some() {
for i in 0..vlen {
let entry_ptr = mmsg_entry_ptr(msgvec_ptr, i);
if let Some(path) = mmsg_entry_named_unix_path(notif, notif_fd, entry_ptr) {
if !path_under_any(&path, &ctx.policy.chroot_writable) {
return NotifAction::Errno(libc::EACCES);
}
}
}
} else {
return sendmmsg_named_unix_on_behalf(
notif,
notif_fd,
sockfd,
msgvec_ptr,
vlen,
flags,
&ctx.policy.chroot_writable,
);
}
}
}
if ctx.policy.has_net_destination_policy {
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 protocol = query_socket_protocol(dup_fd.as_raw_fd());
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 m = match send_msghdr_on_behalf(notif, ctx, notif_fd, &dup_fd, protocol, entry_ptr)
.await
{
Ok(m) => m,
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;
}
}
}
return if sent > 0 {
NotifAction::ReturnValue(sent as i64)
} else {
NotifAction::Errno(first_errno.unwrap_or(ECONNREFUSED))
};
}
for i in 0..vlen {
let entry_ptr = mmsg_entry_ptr(msgvec_ptr, i);
match prescan_msghdr(notif, notif_fd, entry_ptr) {
PrescanResult::OnBehalf => continue,
PrescanResult::ContinueWholeCall => return NotifAction::Continue,
PrescanResult::Errno(e) => return NotifAction::Errno(e),
}
}
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 protocol = match query_socket_protocol(dup_fd.as_raw_fd()) {
Some(p) => p,
None => return NotifAction::Errno(ECONNREFUSED),
};
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 m = match send_msghdr_on_behalf(notif, ctx, notif_fd, &dup_fd, Some(protocol), entry_ptr).await {
Ok(m) => m,
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(ECONNREFUSED))
}
}