use std::os::fd::AsFd;
use libc::{c_int, c_uint, c_void, iovec, sockaddr, socklen_t, MSG_CTRUNC};
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
sys::socket::{SockaddrLike, SockaddrStorage},
};
use zeroize::Zeroizing;
use crate::{
compat::{
mmsghdr, mmsghdr32, msghdr, msghdr32, recvmmsg, recvmsg, try_from_bytes, MmsgHdr, MsgFlags,
TimeSpec32, TimeSpec64, ToByteArray, UIO_MAXIOV,
},
confine::scmp_arch_is_compat32,
fd::{fd_inode, get_nonblock, has_recv_timeout, SafeOwnedFd},
kernel::net::to_msgflags,
req::UNotifyEventRequest,
sandbox::{Flags, Options},
unix::unix_addr_len,
};
pub(crate) fn handle_recvmsg(
fd: SafeOwnedFd,
request: &UNotifyEventRequest,
args: &[u64; 6],
flags: Flags,
options: Options,
) -> Result<ScmpNotifResp, Errno> {
let call_flags = to_msgflags(args[2]);
if !options.allow_unsafe_oob() && call_flags.contains(MsgFlags::MSG_OOB) {
return Err(Errno::EOPNOTSUPP);
}
let req = request.scmpreq;
let is32 = scmp_arch_is_compat32(req.data.arch);
if !is32 && call_flags.contains(MsgFlags::MSG_CMSG_COMPAT) {
return Err(Errno::EINVAL);
}
let hdr_sz = if is32 {
size_of::<msghdr32>()
} else {
size_of::<msghdr>()
};
let hdr = request.read_vec_all_zeroed(args[1], hdr_sz)?;
let mut hdr: msghdr = if is32 {
let m32: msghdr32 = try_from_bytes(&hdr)?;
msghdr::from(m32)
} else {
try_from_bytes(&hdr)?
};
#[expect(clippy::type_complexity)]
let mut msg_bufs: Vec<(Zeroizing<Vec<u8>>, u64)> = Vec::new();
let mut msg_iovs: Vec<iovec> = Vec::new();
let mut nam_buf: Vec<u8> = Vec::new();
let mut ctl_buf: Vec<u8> = Vec::new();
let (user_nam_base, user_nam_size) = request.setup_msghdr_name(&mut hdr, &mut nam_buf)?;
let user_iov_base = request.read_msghdr_iov(&mut hdr, &mut msg_bufs, &mut msg_iovs)?;
let (user_ctl_base, user_ctl_size) = request.setup_msghdr_ctl(&mut hdr, &mut ctl_buf)?;
let is_blocking = !call_flags.contains(MsgFlags::MSG_DONTWAIT) && !get_nonblock(&fd)?;
let ignore_restart = if is_blocking {
has_recv_timeout(&fd)?
} else {
false
};
if is_blocking {
request.cache.add_sys_block(req, ignore_restart)?;
}
let result = recvmsg(&fd, hdr.as_mut(), call_flags);
if is_blocking {
request.cache.del_sys_block(req.id)?;
}
let r_bytes = result?.bytes;
scatter_iov(request, r_bytes, &msg_bufs)?;
let namelen: socklen_t = socklen_t::try_from(hdr.msg_namelen).or(Err(Errno::EINVAL))?;
let (namelen_out, addr_bytes) = prepare_addr(&fd, request, hdr.msg_name, namelen)?;
hdr.msg_namelen = c_int::try_from(namelen_out).or(Err(Errno::EINVAL))?;
if !hdr.msg_control.is_null() && hdr.msg_controllen > 0 {
let cmsg_buf =
unsafe { std::slice::from_raw_parts(hdr.msg_control as *const u8, hdr.msg_controllen) };
let close_on_exec =
flags.force_cloexec() || call_flags.contains(MsgFlags::MSG_CMSG_CLOEXEC);
let rand_fd = flags.force_rand_fd();
let (cmsgs, cmsgs_truncated) =
request.fixup_cmsgs(&fd, cmsg_buf, user_ctl_size, close_on_exec, rand_fd)?;
let (cmsg_len, truncated) = request.write_cmsgs(&cmsgs, user_ctl_base, user_ctl_size)?;
if truncated || cmsgs_truncated {
hdr.msg_flags |= MSG_CTRUNC as c_uint;
}
hdr.msg_controllen = cmsg_len;
} else {
hdr.msg_controllen = 0;
}
hdr.msg_iov = user_iov_base as *mut iovec;
hdr.msg_name = user_nam_base as *mut c_void;
hdr.msg_control = user_ctl_base as *mut c_void;
if is32 {
let m32: msghdr32 = hdr.try_into()?;
let buf: [u8; size_of::<msghdr32>()] = m32.to_byte_array();
request.write_mem_all(&buf, args[1])?;
} else {
let buf: [u8; size_of::<msghdr>()] = hdr.to_byte_array();
request.write_mem_all(&buf, args[1])?;
}
if let Some(bytes) = addr_bytes {
#[expect(clippy::cast_possible_truncation)]
let out_len = (namelen_out.min(user_nam_size as socklen_t)) as usize;
request.write_mem_all(&bytes[..out_len], user_nam_base)?;
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(r_bytes as i64))
}
pub(crate) fn handle_recvmmsg(
fd: SafeOwnedFd,
request: &UNotifyEventRequest,
args: &[u64; 6],
flags: Flags,
options: Options,
) -> Result<ScmpNotifResp, Errno> {
let is32 = scmp_arch_is_compat32(request.scmpreq.data.arch);
let timeout = if args[4] != 0 {
if is32 {
Some(request.remote_timespec32(args[4])?)
} else {
Some(request.remote_timespec64(args[4])?)
}
} else {
None
};
do_recvmmsg(fd, request, args, flags, options, timeout, is32)
}
pub(crate) fn handle_recvmmsg64(
fd: SafeOwnedFd,
request: &UNotifyEventRequest,
args: &[u64; 6],
flags: Flags,
options: Options,
) -> Result<ScmpNotifResp, Errno> {
let timeout = if args[4] != 0 {
Some(request.remote_timespec64(args[4])?)
} else {
None
};
do_recvmmsg(
fd, request, args, flags, options, timeout, false,
)
}
fn do_recvmmsg<Fd: AsFd>(
fd: Fd,
request: &UNotifyEventRequest,
args: &[u64; 6],
flags: Flags,
options: Options,
mut timeout: Option<TimeSpec64>,
timeout_is32: bool,
) -> Result<ScmpNotifResp, Errno> {
let call_flags = to_msgflags(args[3]);
if !options.allow_unsafe_oob() && call_flags.contains(MsgFlags::MSG_OOB) {
return Err(Errno::EOPNOTSUPP);
}
let req = request.scmpreq;
let is32 = scmp_arch_is_compat32(req.data.arch);
if !is32 && call_flags.contains(MsgFlags::MSG_CMSG_COMPAT) {
return Err(Errno::EINVAL);
}
#[expect(clippy::cast_possible_truncation)]
let msg_count = (args[2] as c_uint as usize).min(UIO_MAXIOV);
let msgs_offset = args[1];
let hdr_sz = if is32 {
size_of::<mmsghdr32>()
} else {
size_of::<mmsghdr>()
};
let total_sz = hdr_sz.checked_mul(msg_count).ok_or(Errno::EOVERFLOW)?;
let hdr = request.read_vec_all_zeroed(msgs_offset, total_sz)?;
let mut msgs = Vec::new();
#[expect(clippy::type_complexity)]
let mut msg_bufs: Vec<Option<Vec<(Zeroizing<Vec<u8>>, u64)>>> = Vec::new();
let mut nam_bufs: Vec<Option<Vec<u8>>> = Vec::new();
let mut ctl_bufs: Vec<Option<Vec<u8>>> = Vec::new();
let mut msg_iovs: Vec<Vec<iovec>> = Vec::new();
let mut user_iov_bases: Vec<Option<u64>> = Vec::new();
let mut user_nam_bases: Vec<Option<(u64, usize)>> = Vec::new();
let mut user_ctl_bases: Vec<Option<(u64, usize)>> = Vec::new();
msgs.try_reserve(msg_count).or(Err(Errno::ENOMEM))?;
msg_bufs.try_reserve(msg_count).or(Err(Errno::ENOMEM))?;
nam_bufs.try_reserve(msg_count).or(Err(Errno::ENOMEM))?;
ctl_bufs.try_reserve(msg_count).or(Err(Errno::ENOMEM))?;
msg_iovs.try_reserve(msg_count).or(Err(Errno::ENOMEM))?;
user_iov_bases
.try_reserve(msg_count)
.or(Err(Errno::ENOMEM))?;
user_nam_bases
.try_reserve(msg_count)
.or(Err(Errno::ENOMEM))?;
user_ctl_bases
.try_reserve(msg_count)
.or(Err(Errno::ENOMEM))?;
for chunk in hdr.chunks(hdr_sz) {
let inner: libc::mmsghdr = if is32 {
let m32: mmsghdr32 = try_from_bytes(chunk)?;
mmsghdr::from(m32).into()
} else {
let m64: mmsghdr = try_from_bytes(chunk)?;
m64.into()
};
let mut mmhdr = MmsgHdr::from_raw(inner);
request.setup_mmsghdr_name(mmhdr.as_inner_mut(), &mut nam_bufs, &mut user_nam_bases)?;
request.read_mmsghdr_iov(
mmhdr.as_inner_mut(),
&mut msg_bufs,
&mut msg_iovs,
&mut user_iov_bases,
)?;
request.setup_mmsghdr_ctl(mmhdr.as_inner_mut(), &mut ctl_bufs, &mut user_ctl_bases)?;
msgs.push(mmhdr);
}
let is_blocking = !call_flags.contains(MsgFlags::MSG_DONTWAIT) && !get_nonblock(&fd)?;
let ignore_restart = if is_blocking {
timeout.is_some() || has_recv_timeout(&fd)?
} else {
false
};
if is_blocking {
request.cache.add_sys_block(req, ignore_restart)?;
}
let result = recvmmsg(&fd, &mut msgs[..msg_count], call_flags, timeout.as_mut());
if is_blocking {
request.cache.del_sys_block(req.id)?;
}
let msg_count = result?;
let ctx = MmsgRecv {
fd: &fd,
request,
flags,
call_flags,
is32,
msgs_offset,
};
let mut nmsgs: usize = 0;
#[expect(clippy::needless_range_loop)]
for idx in 0..msg_count {
match ctx.write_one(
&mut msgs[idx],
msg_bufs.get(idx).and_then(Option::as_ref),
user_iov_bases.get(idx).copied().flatten(),
user_nam_bases.get(idx).copied().flatten(),
user_ctl_bases.get(idx).copied().flatten(),
idx,
) {
Ok(()) => nmsgs = nmsgs.checked_add(1).ok_or(Errno::EOVERFLOW)?,
Err(_) if nmsgs > 0 => break,
Err(errno) => return Err(errno),
}
}
if nmsgs > 0 {
if let Some(timeout) = timeout {
let addr = args[4];
if timeout_is32 {
let t32: TimeSpec32 = timeout.try_into()?;
let buf: [u8; size_of::<TimeSpec32>()] = t32.to_byte_array();
request.write_mem_all(&buf, addr)?;
} else {
let buf: [u8; size_of::<TimeSpec64>()] = timeout.to_byte_array();
request.write_mem_all(&buf, addr)?;
}
}
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(nmsgs as i64))
}
#[expect(clippy::type_complexity)]
fn scatter_iov(
request: &UNotifyEventRequest,
bytes: usize,
bufs: &[(Zeroizing<Vec<u8>>, u64)],
) -> Result<(), Errno> {
if bytes > 0 {
let mut remaining = bytes;
for (buf, ptr) in bufs {
if remaining == 0 {
break;
}
let take = remaining.min(buf.len());
request.write_mem_all(&buf[..take], *ptr)?;
remaining = remaining.checked_sub(take).ok_or(Errno::EOVERFLOW)?;
}
}
Ok(())
}
#[expect(clippy::type_complexity)]
fn prepare_addr<Fd: AsFd>(
fd: Fd,
request: &UNotifyEventRequest,
msg_name: *mut c_void,
msg_namelen: socklen_t,
) -> Result<(socklen_t, Option<Vec<u8>>), Errno> {
let r_addr = if !msg_name.is_null() && msg_namelen > 0 {
unsafe { SockaddrStorage::from_raw(msg_name as *const sockaddr, Some(msg_namelen)) }
} else {
None
};
if let Some(mut addr) = r_addr {
let hdr_namelen = if let Ok(ino) = fd_inode(fd) {
if let Ok(peer_addr) = request.resolve_unix_peer(&addr, ino) {
addr = peer_addr;
addr.as_unix_addr().map_or(addr.len(), unix_addr_len)
} else {
msg_namelen
}
} else {
msg_namelen
};
let buf =
unsafe { std::slice::from_raw_parts(addr.as_ptr().cast::<u8>(), addr.len() as usize) };
let namelen_out = addr.len().min(hdr_namelen);
let mut bytes: Vec<u8> = Vec::new();
bytes.try_reserve(buf.len()).or(Err(Errno::ENOMEM))?;
bytes.extend_from_slice(buf);
Ok((namelen_out, Some(bytes)))
} else {
Ok((0, None))
}
}
struct MmsgRecv<'a, Fd: AsFd> {
fd: &'a Fd,
request: &'a UNotifyEventRequest,
flags: Flags,
call_flags: MsgFlags,
is32: bool,
msgs_offset: u64,
}
impl<Fd: AsFd> MmsgRecv<'_, Fd> {
#[expect(clippy::type_complexity)]
fn write_one(
&self,
mmsg_hdr: &mut MmsgHdr,
msg_bufs: Option<&Vec<(Zeroizing<Vec<u8>>, u64)>>,
user_iov_base: Option<u64>,
user_nam_base: Option<(u64, usize)>,
user_ctl_base: Option<(u64, usize)>,
idx: usize,
) -> Result<(), Errno> {
let inner = mmsg_hdr.as_inner_mut();
if let Some(iov_ptr) = user_iov_base {
inner.msg_hdr.msg_iov = iov_ptr as *mut iovec;
}
if let Some(bufs) = msg_bufs {
scatter_iov(self.request, inner.msg_len as usize, bufs)?;
}
let nam_write = if let Some((nam_ptr, nam_len)) = user_nam_base {
let (namelen_out, addr_bytes) = prepare_addr(
self.fd,
self.request,
inner.msg_hdr.msg_name,
inner.msg_hdr.msg_namelen,
)?;
inner.msg_hdr.msg_namelen = namelen_out;
inner.msg_hdr.msg_name = nam_ptr as *mut c_void;
addr_bytes.map(|b| (b, nam_ptr, nam_len, namelen_out))
} else {
None
};
#[expect(clippy::disallowed_methods)]
#[expect(clippy::useless_conversion)]
if let Some((ctl_ptr, ctl_len)) = user_ctl_base {
if !inner.msg_hdr.msg_control.is_null() && inner.msg_hdr.msg_controllen > 0 {
#[expect(clippy::unnecessary_cast)]
let cmsg_buf = unsafe {
std::slice::from_raw_parts(
inner.msg_hdr.msg_control as *const u8,
inner.msg_hdr.msg_controllen as usize,
)
};
let close_on_exec = self.flags.force_cloexec()
|| self.call_flags.contains(MsgFlags::MSG_CMSG_CLOEXEC);
let rand_fd = self.flags.force_rand_fd();
let (cmsgs, cmsgs_truncated) =
self.request
.fixup_cmsgs(self.fd, cmsg_buf, ctl_len, close_on_exec, rand_fd)?;
let (cmsg_len, truncated) = self.request.write_cmsgs(&cmsgs, ctl_ptr, ctl_len)?;
if truncated || cmsgs_truncated {
inner.msg_hdr.msg_flags |= MsgFlags::MSG_CTRUNC.bits();
}
inner.msg_hdr.msg_control = ctl_ptr as *mut c_void;
inner.msg_hdr.msg_controllen = cmsg_len.try_into().unwrap();
} else {
inner.msg_hdr.msg_controllen = 0;
}
} else {
inner.msg_hdr.msg_controllen = 0;
}
let msg_header = mmsg_hdr.to_msg_bytes(self.is32)?;
let msg_header_size = msg_header.len() as u64;
let msg_header_offs = (idx as u64)
.checked_mul(msg_header_size)
.ok_or(Errno::EOVERFLOW)?;
let offset = self
.msgs_offset
.checked_add(msg_header_offs)
.ok_or(Errno::EOVERFLOW)?;
self.request.write_mem_all(&msg_header, offset)?;
if let Some((bytes, nam_ptr, nam_len, namelen_out)) = nam_write {
#[expect(clippy::cast_possible_truncation)]
let out_len = (namelen_out.min(nam_len as socklen_t)) as usize;
self.request.write_mem_all(&bytes[..out_len], nam_ptr)?;
}
Ok(())
}
}