#![forbid(unsafe_code)]
use std::io::IoSlice;
use libc::{c_uint, iovec, size_t, SIGPIPE};
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
sys::{
signal::{SigSet, Signal},
socket::SockaddrStorage,
uio::RemoteIoVec,
},
unistd::Pid,
};
use crate::{
compat::{
addr_family, iovec32, mmsghdr, mmsghdr32, msghdr, msghdr32, pack_cmsg_buf, sendmmsg,
sendmsg, try_from_bytes, AddressFamily, Cmsg, CmsgOwned, MmsgHdr, MsgFlags, MsgHdr,
SockType, PF_ALG, PF_INET, PF_INET6, PF_NETLINK, PF_UNIX, PF_UNSPEC, UIO_MAXIOV,
},
config::MAX_RW_COUNT,
confine::{is_valid_ptr_range, scmp_arch_is_compat32},
cookie::safe_getzerocopy,
fd::{fd_inode, has_send_timeout},
fs::{block_signal, sigtimedpoll, unblock_signal},
ip::{clear_scope6, make_lo6addr, make_loaddr, SockInfo, SocketCall},
kernel::{
net::{
canon_addr, check_send_state, get_port, handle_safe_bind3_v4, handle_safe_bind3_v6,
handle_safe_bind_send, max_send_len, parse_addr, sandbox_addr, to_msgflags, SockOpts,
},
sandbox_path,
},
log_enabled,
lookup::CanonicalPath,
path::XPath,
proc::{info::PROC_INFO, util::proc_tgid},
req::UNotifyEventRequest,
retry::retry_on_eintr,
sandbox::{Capability, Flags, SandboxGuard},
syslog::LogLevel,
warn, xfmt,
};
const SIZEOF_MSG_LEN: usize = size_of::<libc::c_uint>();
#[expect(clippy::cognitive_complexity)]
pub(crate) fn handle_sendmsg(
request: &UNotifyEventRequest,
mut sock: SockInfo,
sock_opts: SockOpts,
args: &[u64; 6],
) -> Result<ScmpNotifResp, Errno> {
let SockOpts {
flags,
options,
is_nonblock,
} = sock_opts;
let msgflags = to_msgflags(args[2]);
if !options.allow_unsafe_oob() && msgflags.contains(MsgFlags::MSG_OOB) {
return Err(Errno::EOPNOTSUPP);
}
let req = request.scmpreq;
let is32 = scmp_arch_is_compat32(req.data.arch);
if !is32 && msgflags.contains(MsgFlags::MSG_CMSG_COMPAT) {
return Err(Errno::EINVAL);
}
let msgflags = msgflags.difference(MsgFlags::MSG_CMSG_COMPAT);
let msgflags = if msgflags.contains(MsgFlags::MSG_ZEROCOPY) {
if safe_getzerocopy(sock.fd())? {
return Err(Errno::ENOBUFS);
}
msgflags.difference(MsgFlags::MSG_ZEROCOPY)
} else {
msgflags
};
let size = if is32 {
size_of::<msghdr32>()
} else {
size_of::<msghdr>()
};
let buf = request.read_vec_all(args[1], size)?;
let msg: msghdr = if is32 {
msghdr::from(try_from_bytes::<msghdr32>(&buf)?)
} else {
try_from_bytes(&buf)?
};
let addr_buf = request.read_msg_name(&msg)?;
let iovecs = check_iov(request, &msg, is32)?;
let cmsg_buf = read_cmsgs(request, &msg)?;
let ctl = match cmsg_buf {
Some(ref buf) => match request.parse_cmsgs(&mut sock, buf) {
Ok(ctl) => Some(ctl),
Err(errno) => {
let has_addr = !msg.msg_name.is_null() && msg.msg_namelen != 0;
check_send_state(request, &mut sock, msgflags, has_addr, is_nonblock)?;
return Err(errno);
}
},
None => None,
};
let send_len = check_iov_len(&mut sock, &iovecs)?;
let addr_arg = match addr_buf {
Some(addr_buf) => parse_addr(
SocketCall::SendMsg,
&mut sock,
sock_opts,
addr_buf,
send_len,
msgflags.contains(MsgFlags::MSG_FASTOPEN),
)?,
None => None,
};
let sandbox = request.get_sandbox();
let addr = check_addr(
request,
&sandbox,
SocketCall::SendMsg,
&mut sock,
sock_opts,
addr_arg,
)?;
let result = check_cmsgs(
request,
&sandbox,
SocketCall::SendMsg,
&mut sock,
ctl,
&addr,
flags,
);
drop(sandbox);
let cmsgs = match result {
Ok(cmsgs) => cmsgs,
Err(errno) => {
check_send_state(request, &mut sock, msgflags, addr.is_some(), is_nonblock)?;
return Err(errno);
}
};
let stream_send = sock.get_stream_send()?;
let io_buffers = match read_iov(request, &iovecs, stream_send) {
Ok(bufs) => bufs,
Err(Errno::EFAULT) => {
let send_len = iovecs
.iter()
.fold(0u64, |len, iov| len.saturating_add(iov.iov_len as u64));
if send_len > 0xFFFF
&& sock.get_dom()? == AddressFamily::Inet
&& matches!(sock.get_stype()?, SockType::Datagram | SockType::Raw)
{
return Err(Errno::EMSGSIZE);
}
check_send_state(request, &mut sock, msgflags, addr.is_some(), is_nonblock)?;
return Err(Errno::EFAULT);
}
Err(errno) => return Err(errno),
};
let mut io_slices: Vec<IoSlice> = Vec::new();
io_slices
.try_reserve_exact(io_buffers.len())
.or(Err(Errno::ENOMEM))?;
for buf in &io_buffers {
io_slices.push(IoSlice::new(buf));
}
let cmsg_slice = cmsgs.as_deref().unwrap_or(&[]);
let mut control_messages: Vec<Cmsg<'_>> = Vec::new();
control_messages
.try_reserve_exact(cmsg_slice.len())
.or(Err(Errno::ENOMEM))?;
for cmsg in cmsg_slice {
control_messages.push(Cmsg::from(cmsg));
}
let unix_data = if sock.get_dom()? == AddressFamily::Unix {
let unix = addr.as_ref().and_then(|addr| addr.addr_arg.as_unix_addr());
request.add_send(sock.fd(), req.pid(), unix).ok()
} else {
None
};
let is_blocking = if !is_nonblock && !msgflags.contains(MsgFlags::MSG_DONTWAIT) {
request
.cache
.add_sys_block(req, has_send_timeout(sock.fd())?)?;
true
} else {
false
};
let result = if let Some(ref addr) = addr {
sendmsg(
sock.fd(),
&io_slices,
&control_messages,
msgflags,
Some(&addr.addr),
)
} else {
sendmsg::<_, SockaddrStorage>(sock.fd(), &io_slices, &control_messages, msgflags, None)
};
if is_blocking {
request.cache.del_sys_block(req.id)?;
}
if result.is_err() {
if let Some((inode, dest)) = unix_data {
let _ = request.del_send(inode, dest);
}
}
if result.is_ok() && options.allow_safe_bind() {
if let Some(ref addr) = addr {
let _ = handle_safe_bind_send(request, SocketCall::SendMsg, &mut sock, &addr.addr);
}
}
#[expect(clippy::cast_possible_wrap)]
Ok(match result {
Ok(n) => request.return_syscall(n as i64),
Err(Errno::EPIPE) if !msgflags.contains(MsgFlags::MSG_NOSIGNAL) => {
if sock.get_send_sigpipe()? {
request.pidfd_kill(SIGPIPE)?;
}
request.fail_syscall(Errno::EPIPE)
}
Err(errno) => request.fail_syscall(errno),
})
}
#[expect(clippy::cognitive_complexity)]
pub(crate) fn handle_sendmmsg(
request: &UNotifyEventRequest,
mut sock: SockInfo,
sock_opts: SockOpts,
args: &[u64; 6],
) -> Result<ScmpNotifResp, Errno> {
let SockOpts {
options,
is_nonblock,
..
} = sock_opts;
let msgflags = to_msgflags(args[3]);
let req = request.scmpreq;
let is32 = scmp_arch_is_compat32(req.data.arch);
if !is32 && msgflags.contains(MsgFlags::MSG_CMSG_COMPAT) {
return Err(Errno::EINVAL);
}
let msgflags = msgflags.difference(MsgFlags::MSG_CMSG_COMPAT);
let addr = args[1];
#[expect(clippy::cast_possible_truncation)]
let vlen = args[2] as c_uint as usize;
if vlen == 0 {
return Ok(request.return_syscall(0));
}
let vlen = vlen.min(UIO_MAXIOV);
let msgflags = if msgflags.contains(MsgFlags::MSG_ZEROCOPY) {
if safe_getzerocopy(sock.fd())? {
return Err(Errno::ENOBUFS);
}
msgflags.difference(MsgFlags::MSG_ZEROCOPY)
} else {
msgflags
};
if !options.allow_unsafe_oob() && msgflags.contains(MsgFlags::MSG_OOB) {
return Err(Errno::EOPNOTSUPP);
}
let mut mmsghdrs = read_mmsghdrs(request, addr, vlen, is32)?;
let (is_blocking, ignore_restart) =
if !is_nonblock && !msgflags.contains(MsgFlags::MSG_DONTWAIT) {
(true, has_send_timeout(sock.fd())?)
} else {
(false, false)
};
let must_signal = !msgflags.contains(MsgFlags::MSG_NOSIGNAL);
let inode = fd_inode(sock.fd())?;
let tgid = proc_tgid(req.pid())?;
if !request.is_valid() {
return Err(Errno::ESRCH);
}
let mut cmsgs: Vec<CheckedMsg> = Vec::new();
cmsgs.try_reserve_exact(vlen).or(Err(Errno::ENOMEM))?;
let mut check_error = None;
let sandbox = request.get_sandbox();
for mmsg in &mmsghdrs {
let msg = match check_msg(
request,
&sandbox,
&mut sock,
sock_opts,
(&mmsg.msg_hdr, msgflags, is32),
(inode, tgid),
) {
Ok(msg) => msg,
Err(errno) if cmsgs.is_empty() => {
check_error = Some(errno);
break;
}
Err(_) => break, };
cmsgs.push(msg);
}
drop(sandbox);
if let Some(errno) = check_error {
let has_addr = match mmsghdrs.first() {
Some(mmsg) => !mmsg.msg_hdr.msg_name.is_null() && mmsg.msg_hdr.msg_namelen != 0,
None => false,
};
check_send_state(request, &mut sock, msgflags, has_addr, is_nonblock)?;
return Err(errno);
}
let mut msg_io_bufs: Vec<Vec<Vec<u8>>> = Vec::new();
let mut msg_cmsg_bufs: Vec<Vec<u8>> = Vec::new();
let msg_count = cmsgs.len();
msg_io_bufs
.try_reserve_exact(msg_count)
.or(Err(Errno::ENOMEM))?;
msg_cmsg_bufs
.try_reserve_exact(msg_count)
.or(Err(Errno::ENOMEM))?;
let is_stream = sock.get_stype()? == SockType::Stream;
let stream_send = sock.get_stream_send()?;
for msg in cmsgs.iter().take(msg_count) {
let io_bufs = match read_iov(request, &msg.iovecs, stream_send) {
Ok(vec) => vec,
Err(errno) if msg_io_bufs.is_empty() => {
if errno == Errno::EFAULT {
let send_len = msg
.iovecs
.iter()
.fold(0u64, |len, iov| len.saturating_add(iov.iov_len as u64));
if send_len > 0xFFFF
&& sock.get_dom()? == AddressFamily::Inet
&& matches!(sock.get_stype()?, SockType::Datagram | SockType::Raw)
{
return Err(Errno::EMSGSIZE);
}
check_send_state(
request,
&mut sock,
msgflags,
msg.addr.is_some(),
is_nonblock,
)?;
}
return Err(errno);
}
Err(_) => break, };
let cmsg_buf = match pack_cmsgs(&msg.cmsgs) {
Ok(vec) => vec,
Err(errno) if msg_io_bufs.is_empty() => return Err(errno),
Err(_) => break, };
let is_partial = stream_send
&& io_bufs.iter().map(Vec::len).sum::<usize>()
< msg
.iovecs
.iter()
.fold(0usize, |len, iov| len.saturating_add(iov.iov_len))
.min(*MAX_RW_COUNT);
msg_io_bufs.push(io_bufs);
msg_cmsg_bufs.push(cmsg_buf);
if is_partial {
break;
}
}
let msg_count = msg_io_bufs.len();
let mut msg_io_slices: Vec<Vec<IoSlice<'_>>> = Vec::new();
msg_io_slices
.try_reserve_exact(msg_count)
.or(Err(Errno::ENOMEM))?;
for io_bufs in &msg_io_bufs {
let mut slices = Vec::new();
slices
.try_reserve_exact(io_bufs.len())
.or(Err(Errno::ENOMEM))?;
for buf in io_bufs {
slices.push(IoSlice::new(buf));
}
msg_io_slices.push(slices);
}
let mut mmsghdr_vec =
build_mmsghdr_vec(&cmsgs[..msg_count], &msg_io_slices, &mut msg_cmsg_bufs)?;
let hdr_count = msg_count
.checked_mul(size_of::<u32>())
.ok_or(Errno::EOVERFLOW)?;
let mut hdr_buf: Vec<u8> = Vec::new();
hdr_buf
.try_reserve_exact(hdr_count)
.or(Err(Errno::ENOMEM))?;
let mut iovs_l: Vec<IoSlice<'_>> = Vec::new();
iovs_l.try_reserve_exact(msg_count).or(Err(Errno::ENOMEM))?;
let mut iovs_r: Vec<RemoteIoVec> = Vec::new();
iovs_r.try_reserve_exact(msg_count).or(Err(Errno::ENOMEM))?;
if must_signal {
block_signal(SIGPIPE)?;
}
if is_blocking {
request.cache.add_sys_block(req, ignore_restart)?;
}
let result = sendmmsg(sock.fd(), &mut mmsghdr_vec[..msg_count], msgflags);
if is_blocking {
request.cache.del_sys_block(req.id)?;
}
let epipe = if must_signal {
let caught = matches!(result, Ok(n) if n < msg_count) && {
let mut set = SigSet::empty();
set.add(Signal::SIGPIPE);
retry_on_eintr(|| sigtimedpoll(&set, None)).is_ok()
};
unblock_signal(SIGPIPE)?;
caught
} else {
false
};
let datagrams = match result {
Ok(n) => n,
Err(Errno::EPIPE) if must_signal => {
delete_senders(request, &cmsgs);
if sock.get_dom()? != AddressFamily::Unix || is_stream {
let _ = request.pidfd_kill(SIGPIPE);
}
return Err(Errno::EPIPE);
}
Err(errno) => {
delete_senders(request, &cmsgs);
return Err(errno);
}
};
if epipe && (sock.get_dom()? != AddressFamily::Unix || is_stream) {
let _ = request.pidfd_kill(SIGPIPE);
}
delete_senders(request, &cmsgs[datagrams..]);
if datagrams > 0
&& options.allow_safe_bind()
&& matches!(sock.get_dom()?, AddressFamily::Inet | AddressFamily::Inet6)
&& cmsgs[..datagrams].iter().any(|msg| msg.addr.is_some())
{
match get_port(sock.fd()) {
Ok(port) if port != 0 => {
let dsts = cmsgs[..datagrams]
.iter()
.filter_map(|msg| msg.addr.as_ref().map(|a| &a.addr));
let _ = if sock.get_dom()? == AddressFamily::Inet {
handle_safe_bind3_v4(request, port, dsts)
} else {
handle_safe_bind3_v6(request, port, dsts)
};
}
_ => {}
}
}
let msg_count = write_mmsghdrs(
request,
&mut mmsghdrs,
&mmsghdr_vec,
(&mut hdr_buf, &mut iovs_l, &mut iovs_r),
(datagrams, addr, is32),
)?;
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(msg_count as i64))
}
struct CheckedAddr {
root: Option<CanonicalPath>,
addr: SockaddrStorage,
addr_arg: SockaddrStorage,
}
struct UnixSender {
ino: u64,
dst: Option<(u32, u32)>, }
struct CheckedMsg {
iovecs: Vec<iovec>,
msgflags: MsgFlags,
addr: Option<CheckedAddr>,
cmsgs: Option<Vec<CmsgOwned>>,
sender: Option<UnixSender>,
}
#[expect(clippy::cognitive_complexity)]
fn check_addr(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard<'_>,
subcall: SocketCall,
sock: &mut SockInfo,
sock_opts: SockOpts,
addr_arg: Option<SockaddrStorage>,
) -> Result<Option<CheckedAddr>, Errno> {
let SockOpts { options, .. } = sock_opts;
let addr_arg = match addr_arg {
Some(addr_arg) => addr_arg,
None => return Ok(None),
};
let any_addr = sandbox.flags.allow_unsafe_any_addr();
let local_net = sandbox.flags.force_local_net();
let (mut addr, root) = canon_addr(request, sandbox, &addr_arg, Capability::CAP_NET_CONNECT)?;
match addr_family(&addr) {
PF_UNIX => {
sandbox_addr(
request,
sandbox,
subcall,
&addr,
&root,
Capability::CAP_NET_CONNECT,
None,
)?;
}
PF_INET => {
if !any_addr {
if let Err(errno) = make_loaddr(subcall, &mut addr, local_net) {
if log_enabled!(LogLevel::Warn) {
let port = addr.as_sockaddr_in().map_or(0, |sin| sin.port());
warn!("ctx": "net", "op": "deny_any_addr",
"sys": subcall.name(), "pid": request.scmpreq.pid().as_raw(),
"err": errno as i32, "addr": xfmt!("0.0.0.0!{port}"),
"msg": xfmt!("denied {} to anyaddr 0.0.0.0!{port}", subcall.name()),
"tip": "configure `trace/allow_unsafe_any_addr:1'");
}
return Err(errno);
}
}
let ip_proto = sock.get_ipproto()?;
sandbox_addr(
request,
sandbox,
subcall,
&addr,
&root,
Capability::CAP_NET_CONNECT,
ip_proto,
)?;
}
PF_INET6 => {
if !any_addr {
if let Err(errno) = make_lo6addr(subcall, &mut addr, local_net) {
if log_enabled!(LogLevel::Warn) {
let port = addr.as_sockaddr_in6().map_or(0, |sin6| sin6.port());
warn!("ctx": "net", "op": "deny_any_addr",
"sys": subcall.name(), "pid": request.scmpreq.pid().as_raw(),
"err": errno as i32, "addr": xfmt!("::!{port}"),
"msg": xfmt!("denied {} to anyaddr ::!{port}", subcall.name()),
"tip": "configure `trace/allow_unsafe_any_addr:1'");
}
return Err(errno);
}
}
if !sandbox.flags.allow_unsafe_ipv6_scope() {
if let Some((scope_id, ip, port)) = clear_scope6(&mut addr) {
warn!("ctx": "net", "op": "zero_scope_id",
"sys": subcall.name(), "pid": request.scmpreq.pid().as_raw(),
"addr": xfmt!("{ip}!{port}"), "scope_id": scope_id,
"msg": xfmt!("zeroed sin6_scope_id={scope_id} on {ip}!{port}"),
"tip": "configure `trace/allow_unsafe_ipv6_scope:1'");
}
}
let ip_proto = sock.get_ipproto()?;
sandbox_addr(
request,
sandbox,
subcall,
&addr,
&root,
Capability::CAP_NET_CONNECT,
ip_proto,
)?;
}
PF_UNSPEC => {
}
PF_ALG | PF_NETLINK => {}
_ if options.allow_unsupp_socket() => {}
_ => return Err(sock.get_send_errno()?),
}
Ok(Some(CheckedAddr {
addr,
root,
addr_arg,
}))
}
fn read_cmsgs(request: &UNotifyEventRequest, msg: &msghdr) -> Result<Option<Vec<u8>>, Errno> {
#[expect(clippy::useless_conversion)]
if usize::try_from(msg.msg_iovlen).or(Err(Errno::EMSGSIZE))? > UIO_MAXIOV {
return Err(Errno::EMSGSIZE);
}
if msg.msg_controllen >= PROC_INFO.optmem_max as size_t {
return Err(Errno::ENOBUFS);
}
if msg.msg_controllen == 0 {
return Ok(None);
}
if msg.msg_control.is_null() {
return Err(Errno::EFAULT);
}
#[expect(clippy::useless_conversion)]
let cmsg_len = usize::try_from(msg.msg_controllen)
.or(Err(Errno::EINVAL))?
.min(*MAX_RW_COUNT);
request
.read_vec_all(msg.msg_control as u64, cmsg_len)
.map(Some)
}
#[expect(clippy::cognitive_complexity)]
fn check_cmsgs(
request: &UNotifyEventRequest,
sandbox: &crate::sandbox::SandboxGuard<'_>,
subcall: SocketCall,
sock: &mut SockInfo,
control_data: Option<Vec<CmsgOwned>>,
checked_addr: &Option<CheckedAddr>,
flags: Flags,
) -> Result<Option<Vec<CmsgOwned>>, Errno> {
let mut control_data = if let Some(control_data) = control_data {
control_data
} else {
return Ok(None);
};
if !sandbox.options.allow_unsafe_ip_pktinfo() {
let cmsg_count_orig = control_data.len();
control_data.retain(|cmsg| {
!matches!(
cmsg,
CmsgOwned::Ipv4PacketInfo(_) | CmsgOwned::Ipv6PacketInfo(_)
)
});
if control_data.len() != cmsg_count_orig {
warn!("ctx": "net", "op": "strip_pktinfo",
"sys": subcall.name(), "pid": request.scmpreq.pid().as_raw(),
"msg": "stripped IP_PKTINFO and/or IPV6_PKTINFO control messages",
"tip": "configure `trace/allow_unsafe_ip_pktinfo:1'");
}
}
if !sandbox.flags.allow_unsafe_ip_retopts() {
let cmsg_count_orig = control_data.len();
control_data.retain(|cmsg| !matches!(cmsg, CmsgOwned::Ipv4ReturnOpts(_)));
if control_data.len() != cmsg_count_orig {
warn!("ctx": "net", "op": "strip_retopts",
"sys": subcall.name(), "pid": request.scmpreq.pid().as_raw(),
"msg": "stripped IP_RETOPTS control message",
"tip": "configure `trace/allow_unsafe_ip_retopts:1'");
}
}
if !sandbox.flags.allow_unsafe_ipv6_rthdr() {
let cmsg_count_orig = control_data.len();
control_data.retain(|cmsg| !matches!(cmsg, CmsgOwned::Ipv6RoutingHdr(_)));
if control_data.len() != cmsg_count_orig {
warn!("ctx": "net", "op": "strip_rthdr",
"sys": subcall.name(), "pid": request.scmpreq.pid().as_raw(),
"msg": "stripped IPV6_RTHDR control message",
"tip": "configure `trace/allow_unsafe_ipv6_rthdr:1'");
}
}
if !sandbox.flags.allow_unsupp_cmsg() {
let cmsg_count_orig = control_data.len();
control_data.retain(|cmsg| !matches!(cmsg, CmsgOwned::Unknown(_)));
if control_data.len() != cmsg_count_orig {
warn!("ctx": "net", "op": "strip_unsupp_cmsg",
"sys": subcall.name(), "pid": request.scmpreq.pid().as_raw(),
"msg": "stripped unsupported control message",
"tip": "configure `trace/allow_unsupp_cmsg:1'");
}
}
let is_unix = sock.get_dom()? == AddressFamily::Unix;
let has_fds = is_unix
&& control_data
.iter()
.any(|cmsg| matches!(cmsg, CmsgOwned::ScmRights(..)));
if has_fds {
if let Some(ref ca) = checked_addr {
sandbox_addr(
request,
sandbox,
subcall,
&ca.addr,
&ca.root,
Capability::CAP_NET_SENDFD,
None,
)?;
} else {
sandbox_path(
Some(request),
sandbox,
request.scmpreq.pid(),
None, XPath::from_bytes(b"!unnamed"),
Capability::CAP_NET_SENDFD,
subcall.name(),
)?;
}
}
if is_unix {
let log_scmp = sandbox.log_scmp();
for cmsg in &control_data {
if let CmsgOwned::ScmRights(fds) = cmsg {
for fd in fds {
request.check_scm_rights(fd, flags, subcall, log_scmp)?;
}
}
}
}
Ok(Some(control_data))
}
fn check_iov(request: &UNotifyEventRequest, msg: &msghdr, is32: bool) -> Result<Vec<iovec>, Errno> {
#[expect(clippy::useless_conversion)]
let len = usize::try_from(msg.msg_iovlen).or(Err(Errno::EMSGSIZE))?;
if len > UIO_MAXIOV {
return Err(Errno::EMSGSIZE);
}
if len == 0 {
return Ok(Vec::new());
}
if msg.msg_iov.is_null() {
return Err(Errno::EFAULT);
}
let size = if is32 {
len.checked_mul(size_of::<iovec32>())
} else {
len.checked_mul(size_of::<iovec>())
}
.ok_or(Errno::EMSGSIZE)?;
let buf = request.read_vec_all(msg.msg_iov as u64, size)?;
let mut iovecs: Vec<iovec> = Vec::new();
iovecs.try_reserve_exact(len).or(Err(Errno::ENOMEM))?;
if is32 {
for chunk in buf.chunks(size_of::<iovec32>()) {
let iov32: iovec32 = try_from_bytes(chunk)?;
iovecs.push(iov32.into());
}
} else {
for chunk in buf.chunks(size_of::<iovec>()) {
iovecs.push(try_from_bytes(chunk)?);
}
}
let iov_cap: usize = if is32 {
i32::MAX as usize
} else {
isize::MAX as usize
};
for iov in &iovecs {
if iov.iov_len > iov_cap {
return Err(Errno::EINVAL);
}
}
let arch = request.scmpreq.data.arch;
for iov in &iovecs {
if !is_valid_ptr_range(iov.iov_base as u64, iov.iov_len as u64, arch) {
return Err(Errno::EFAULT);
}
}
Ok(iovecs)
}
fn check_iov_len(sock: &mut SockInfo, iovecs: &[iovec]) -> Result<usize, Errno> {
let send_len = iovecs
.iter()
.fold(0usize, |len, iov| len.saturating_add(iov.iov_len))
.min(*MAX_RW_COUNT);
sock.check_send_len(send_len)?;
Ok(send_len)
}
#[expect(clippy::type_complexity)]
fn build_iov(iovecs: &[iovec]) -> Result<(Vec<Vec<u8>>, Vec<RemoteIoVec>), Errno> {
let mut io_buffers: Vec<Vec<u8>> = Vec::new();
let mut remote_iovs: Vec<RemoteIoVec> = Vec::new();
io_buffers
.try_reserve_exact(iovecs.len())
.or(Err(Errno::ENOMEM))?;
remote_iovs
.try_reserve_exact(iovecs.len())
.or(Err(Errno::ENOMEM))?;
let mut total_len: usize = 0;
for iov in iovecs {
if iov.iov_len == 0 {
continue;
}
let rem_len = max_send_len()
.checked_sub(total_len)
.ok_or(Errno::EOVERFLOW)?;
#[expect(clippy::unnecessary_cast)]
let iov_len = (iov.iov_len as usize).min(rem_len);
let mut data_buf = Vec::new();
data_buf.try_reserve_exact(iov_len).or(Err(Errno::ENOMEM))?;
io_buffers.push(data_buf);
remote_iovs.push(RemoteIoVec {
base: iov.iov_base as usize,
len: iov_len,
});
total_len = total_len.checked_add(iov_len).ok_or(Errno::EOVERFLOW)?;
}
Ok((io_buffers, remote_iovs))
}
fn read_iov(
request: &UNotifyEventRequest,
iovecs: &[iovec],
is_stream: bool,
) -> Result<Vec<Vec<u8>>, Errno> {
let (mut io_buffers, remote_iovs) = build_iov(iovecs)?;
if !io_buffers.is_empty() {
if is_stream {
if request.read_mem_iov(&mut io_buffers, &remote_iovs)? == 0 {
return Err(Errno::EFAULT);
}
} else {
request.read_mem_iov_all(&mut io_buffers, &remote_iovs)?;
}
}
Ok(io_buffers)
}
fn pack_cmsgs(data: &Option<Vec<CmsgOwned>>) -> Result<Vec<u8>, Errno> {
let data = match data {
Some(data) => data,
None => return Ok(Vec::new()),
};
let mut cmsgs: Vec<Cmsg<'_>> = Vec::new();
cmsgs.try_reserve_exact(data.len()).or(Err(Errno::ENOMEM))?;
for cmsg in data {
cmsgs.push(Cmsg::from(cmsg));
}
pack_cmsg_buf(&cmsgs)
}
fn read_mmsghdrs(
request: &UNotifyEventRequest,
addr: u64,
vlen: usize,
is32: bool,
) -> Result<Vec<mmsghdr>, Errno> {
let mmsghdr_size = if is32 {
size_of::<mmsghdr32>()
} else {
size_of::<mmsghdr>()
};
let size = vlen.checked_mul(mmsghdr_size).ok_or(Errno::EINVAL)?;
let buf = request.read_vec(addr, size)?;
if buf.len() < mmsghdr_size {
return Err(Errno::EFAULT);
}
let mut mmsghdrs: Vec<mmsghdr> = Vec::new();
mmsghdrs.try_reserve_exact(vlen).or(Err(Errno::ENOMEM))?;
for chunk in buf.chunks_exact(mmsghdr_size) {
if is32 {
let m: mmsghdr32 = try_from_bytes(chunk)?;
mmsghdrs.push(m.into());
} else {
mmsghdrs.push(try_from_bytes(chunk)?);
}
}
Ok(mmsghdrs)
}
fn check_msg(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
sock: &mut SockInfo,
sock_opts: SockOpts,
msg_data: (&msghdr, MsgFlags, bool),
ino_data: (u64, Pid),
) -> Result<CheckedMsg, Errno> {
let SockOpts { flags, .. } = sock_opts;
let (msghdr, msgflags, is32) = msg_data;
let (inode, tgid) = ino_data;
let msgflags = msgflags | (to_msgflags(msghdr.msg_flags.into()) & MsgFlags::MSG_EOR);
let addr_buf = request.read_msg_name(msghdr)?;
let iovecs = check_iov(request, msghdr, is32)?;
let cmsg_buf = read_cmsgs(request, msghdr)?;
let ctl = match cmsg_buf {
Some(ref buf) => Some(request.parse_cmsgs(sock, buf)?),
None => None,
};
let send_len = check_iov_len(sock, &iovecs)?;
let addr_arg = match addr_buf {
Some(addr_buf) => parse_addr(
SocketCall::SendMmsg,
sock,
sock_opts,
addr_buf,
send_len,
msgflags.contains(MsgFlags::MSG_FASTOPEN),
)?,
None => None,
};
let addr = check_addr(
request,
sandbox,
SocketCall::SendMmsg,
sock,
sock_opts,
addr_arg,
)?;
let cmsgs = check_cmsgs(
request,
sandbox,
SocketCall::SendMmsg,
sock,
ctl,
&addr,
flags,
)?;
let sender = if sock.get_dom()? == AddressFamily::Unix {
let unix = addr.as_ref().and_then(|addr| addr.addr_arg.as_unix_addr());
request
.add_send2(inode, tgid, unix)
.ok()
.map(|(ino, dst)| UnixSender { ino, dst })
} else {
None
};
Ok(CheckedMsg {
msgflags,
addr,
cmsgs,
sender,
iovecs,
})
}
#[expect(clippy::type_complexity)]
fn write_mmsghdrs<'a>(
request: &UNotifyEventRequest,
mmsghdrs: &mut [mmsghdr],
mmsghdr_vec: &[MmsgHdr],
mmsg_bufs: (
&'a mut Vec<u8>,
&mut Vec<IoSlice<'a>>,
&mut Vec<RemoteIoVec>,
),
mmsg_info: (usize, u64, bool),
) -> Result<usize, Errno> {
let (datagrams, addr, is32) = mmsg_info;
let (mmsghdr_size, msg_len_off) = if is32 {
(
size_of::<mmsghdr32>(),
std::mem::offset_of!(mmsghdr32, msg_len),
)
} else {
(size_of::<mmsghdr>(), std::mem::offset_of!(mmsghdr, msg_len))
};
let (hdr_buf, iovs_l, iovs_r) = mmsg_bufs;
for i in 0..datagrams {
mmsghdrs[i].msg_len = mmsghdr_vec[i].msg_len();
hdr_buf.extend_from_slice(&mmsghdrs[i].msg_len.to_ne_bytes());
}
for i in 0..datagrams {
let buf_off = i.checked_mul(SIZEOF_MSG_LEN).ok_or(Errno::EOVERFLOW)?;
let hdr_off = (i as u64)
.checked_mul(mmsghdr_size as u64)
.ok_or(Errno::EOVERFLOW)?
.checked_add(msg_len_off as u64)
.ok_or(Errno::EOVERFLOW)?;
let addr = addr.checked_add(hdr_off).ok_or(Errno::EOVERFLOW)?;
#[expect(clippy::arithmetic_side_effects)]
iovs_l.push(IoSlice::new(&hdr_buf[buf_off..buf_off + SIZEOF_MSG_LEN]));
iovs_r.push(RemoteIoVec {
base: usize::try_from(addr).or(Err(Errno::EOVERFLOW))?,
len: SIZEOF_MSG_LEN,
});
}
if iovs_l.is_empty() {
return Ok(0);
}
let n = request.write_mem_many_all(iovs_l, iovs_r)?;
let msg_count = n / SIZEOF_MSG_LEN;
if msg_count == 0 && datagrams > 0 {
Err(Errno::EFAULT)
} else {
Ok(msg_count)
}
}
fn delete_senders(request: &UNotifyEventRequest, msgs: &[CheckedMsg]) {
for msg in msgs {
if let Some(ref sender) = msg.sender {
let _ = request.del_send(sender.ino, sender.dst);
}
}
}
fn build_mmsghdr_vec(
msgs: &[CheckedMsg],
io_slices: &[Vec<IoSlice<'_>>],
cmsg_bufs: &mut [Vec<u8>],
) -> Result<Vec<MmsgHdr>, Errno> {
let count = msgs.len();
let mut vec: Vec<MmsgHdr> = Vec::new();
vec.try_reserve_exact(count).or(Err(Errno::ENOMEM))?;
for i in 0..count {
let mut mhdr = MsgHdr::default();
if let Some(ref addr) = msgs[i].addr {
mhdr.set_addr(&addr.addr);
}
mhdr.set_iov(&io_slices[i]);
mhdr.set_control(&mut cmsg_bufs[i]);
mhdr.set_flags(msgs[i].msgflags.bits());
let mut mmhdr = MmsgHdr::default();
mmhdr.set_msg_hdr(mhdr.into_inner());
vec.push(mmhdr);
}
Ok(vec)
}