use std::{
net::IpAddr,
ops::Deref,
os::{
fd::{AsFd, AsRawFd, RawFd},
unix::ffi::OsStrExt,
},
};
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use iprange::IpRange;
use libc::SIGPIPE;
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
fcntl::OFlag,
sys::socket::{SockaddrLike, SockaddrStorage, UnixAddr},
NixPath,
};
use crate::{
caps::{util::capable, Capabilities},
compat::{
addr_family, sockaddr_family, AddressFamily, MsgFlags, SockType, PF_ALG, PF_INET, PF_INET6,
PF_MAX, PF_NETLINK, PF_UNIX, PF_UNSPEC,
},
config::{HOOK_SCKCALLS, MAX_RW_COUNT},
confine::{is_valid_ptr_range, scmp_arch_bits},
cookie::{
safe_exit_group, safe_getpeername, safe_getsockname, safe_getsocktcpstate,
safe_getsocktfoconnect, TcpState,
},
fd::{fd_status_flags, route_open, route_query_src, route_src},
info,
ip::{
clear_scope6, has_privileged_port_v4, has_privileged_port_v6, make_lo6addr, make_loaddr,
IpProto, SockInfo, SocketCall, SOCKETCALL_NARGS,
},
kernel::net::{
accept::handle_accept,
bind::handle_bind,
connect::handle_connect,
getpeername::handle_getpeername,
getsockname::handle_getsockname,
getsockopt::handle_getsockopt,
recvfrom::{handle_recv, handle_recvfrom},
recvmsg::{handle_recvmmsg, handle_recvmmsg64, handle_recvmsg},
sendmsg::{handle_sendmmsg, handle_sendmsg},
sendto::handle_sendto,
socket::{handle_socket, handle_socketpair},
},
log_enabled,
lookup::{file_type, safe_canonicalize, CanonicalPath, FileType, FsFlags},
path::{XPath, XPathBuf, XPathCow},
proc::info::PROC_INFO,
req::UNotifyEventRequest,
sandbox::{Action, AddressPattern, Capability, CidrRule, Flags, Options, SandboxGuard},
syslog::LogLevel,
warn, xfmt,
};
pub(crate) mod accept;
pub(crate) mod bind;
pub(crate) mod connect;
pub(crate) mod getpeername;
pub(crate) mod getsockname;
pub(crate) mod getsockopt;
pub(crate) mod recvfrom;
pub(crate) mod recvmsg;
pub(crate) mod sendmsg;
pub(crate) mod sendto;
pub(crate) mod socket;
const UNIX_PATH_MAX: usize = 108;
#[derive(Copy, Clone, Debug)]
pub(crate) struct SockOpts {
pub flags: Flags,
pub options: Options,
pub is_nonblock: bool,
}
pub(crate) enum NetAddr {
None,
Inet(IpAddr, u16),
UnixPath(CanonicalPath),
Unix(XPathBuf),
UnixUnnamed,
}
pub(crate) fn sys_socketcall(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let call = req.data.args[0] as i32;
let call: u8 = match call.try_into() {
Ok(call) if (1..=20).contains(&call) => call,
_ => return request.fail_syscall(Errno::EINVAL),
};
let sandbox = request.get_sandbox();
let restrict_socketcall = !sandbox.options.allow_unsafe_socketcall();
drop(sandbox);
if restrict_socketcall {
return request.fail_syscall(Errno::ENOSYS);
} else if HOOK_SCKCALLS.binary_search(&call).is_err() {
return unsafe { request.continue_syscall() };
}
let subcall: SocketCall = match SocketCall::try_from(call) {
Ok(SocketCall::RecvMmsg64) | Err(_) => return request.fail_syscall(Errno::EINVAL),
Ok(subcall) => subcall,
};
let is32 = scmp_arch_bits(req.data.arch) == 32;
let sizeof_ulong: usize = if is32 { 4 } else { 8 };
const ARGLEN: usize = 6;
let mut args = [0u64; ARGLEN];
let narg = match SOCKETCALL_NARGS.get(usize::from(call)) {
Some(&n) => usize::from(n),
None => return request.fail_syscall(Errno::EINVAL),
};
let bufsiz = sizeof_ulong.saturating_mul(narg);
match request.read_vec_all(req.data.args[1], bufsiz) {
Ok(buf) => {
for (i, chunk) in buf.chunks_exact(sizeof_ulong).enumerate() {
match sizeof_ulong {
4 => match chunk.try_into() {
Ok(bytes) => args[i] = u64::from(u32::from_ne_bytes(bytes)),
Err(_) => return request.fail_syscall(Errno::EFAULT),
},
8 => match chunk.try_into() {
Ok(bytes) => args[i] = u64::from_ne_bytes(bytes),
Err(_) => return request.fail_syscall(Errno::EFAULT),
},
_ => {
unreachable!("BUG: Invalid sizeof unsigned long: {sizeof_ulong}!");
}
}
}
}
Err(errno) => return request.fail_syscall(errno),
}
syscall_network_handler(request, subcall, &args)
}
pub(crate) fn sys_socket(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::Socket, &req.data.args)
}
pub(crate) fn sys_socketpair(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::SocketPair, &req.data.args)
}
pub(crate) fn sys_bind(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::Bind, &req.data.args)
}
pub(crate) fn sys_accept(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::Accept, &req.data.args)
}
pub(crate) fn sys_accept4(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::Accept4, &req.data.args)
}
pub(crate) fn sys_getpeername(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::GetPeerName, &req.data.args)
}
pub(crate) fn sys_getsockname(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::GetSockName, &req.data.args)
}
pub(crate) fn sys_getsockopt(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::GetSockOpt, &req.data.args)
}
pub(crate) fn sys_connect(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::Connect, &req.data.args)
}
pub(crate) fn sys_recvfrom(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::RecvFrom, &req.data.args)
}
pub(crate) fn sys_recvmsg(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::RecvMsg, &req.data.args)
}
pub(crate) fn sys_recvmmsg(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::RecvMmsg, &req.data.args)
}
pub(crate) fn sys_recvmmsg64(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::RecvMmsg64, &req.data.args)
}
pub(crate) fn sys_sendto(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::SendTo, &req.data.args)
}
pub(crate) fn sys_sendmsg(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::SendMsg, &req.data.args)
}
pub(crate) fn sys_sendmmsg(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
syscall_network_handler(request, SocketCall::SendMmsg, &req.data.args)
}
#[expect(clippy::cognitive_complexity)]
fn syscall_network_handler(
request: UNotifyEventRequest,
subcall: SocketCall,
args: &[u64; 6],
) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let sandbox = request.get_sandbox();
let flags = *sandbox.flags;
let options = *sandbox.options;
let allow_safe_bind = options.allow_safe_bind();
let allow_unsafe_kcapi = options.allow_unsafe_kcapi();
let allow_unsupp_socket = options.allow_unsupp_socket();
let restrict_oob = !options.allow_unsafe_oob();
let restrict_recvmsg = !options.allow_unsafe_recvmsg();
let randomize_fds = flags.force_rand_fd();
let cap = match subcall {
SocketCall::Socket => {
let nlfam = *sandbox.netlink_families;
drop(sandbox); return handle_socket(&request, args, flags, options, nlfam);
}
SocketCall::SocketPair => {
return handle_socketpair(&request, sandbox, SocketCall::SocketPair, args);
}
SocketCall::RecvMsg | SocketCall::RecvMmsg | SocketCall::RecvMmsg64 if !restrict_recvmsg => {
return Ok(unsafe { request.continue_syscall() });
}
SocketCall::Accept | SocketCall::GetSockName | SocketCall::GetPeerName | SocketCall::Recv | SocketCall::RecvFrom | SocketCall::RecvMsg | SocketCall::Accept4 | SocketCall::RecvMmsg | SocketCall::RecvMmsg64 => {
Capability::empty()
}
SocketCall::Bind => Capability::CAP_NET_BIND,
_ => Capability::CAP_NET_CONNECT,
};
drop(sandbox);
if matches!(
subcall,
SocketCall::Send | SocketCall::SendTo | SocketCall::Recv | SocketCall::RecvFrom
) && args[1] != 0
&& !is_valid_ptr_range(
args[1],
args[2].min(*MAX_RW_COUNT as u64),
request.scmpreq.data.arch,
)
{
return Err(Errno::EFAULT);
}
let fd = request.get_fd(args[0] as RawFd)?;
let status_flags = fd_status_flags(&fd)?;
if status_flags.contains(OFlag::O_PATH) {
return Err(Errno::EBADF);
}
let is_nonblock = status_flags.contains(OFlag::O_NONBLOCK);
#[expect(clippy::cast_possible_truncation)]
if subcall == SocketCall::Accept4
&& (args[3] as libc::c_int) & !(libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK) != 0
{
return Err(Errno::EINVAL);
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
if subcall == SocketCall::Connect {
let addr_len = args[2] as libc::c_int;
if addr_len < 0 || addr_len as usize > size_of::<libc::sockaddr_storage>() {
return Err(Errno::EINVAL);
}
if addr_len > 0 && args[1] == 0 {
return Err(Errno::EFAULT);
}
}
if file_type(&fd, None, false)? != FileType::Sock {
return Err(
if subcall == SocketCall::Connect
&& !is_valid_ptr_range(args[1], args[2], request.scmpreq.data.arch)
{
Errno::EFAULT
} else {
Errno::ENOTSOCK
},
);
}
let sock_opts = SockOpts {
flags,
options,
is_nonblock,
};
let mut sock = SockInfo::new(fd);
match subcall {
SocketCall::Accept | SocketCall::Accept4 => {
return handle_accept(&request, subcall, sock, args, is_nonblock);
}
SocketCall::GetSockName => {
return handle_getsockname(&request, sock, args);
}
SocketCall::GetPeerName => {
return handle_getpeername(&request, sock, args);
}
SocketCall::Send => {
return handle_sendto(&request, sock, sock_opts, None, args);
}
SocketCall::Recv => {
return handle_recv(&request, sock, args, restrict_oob, is_nonblock);
}
SocketCall::RecvFrom => {
return handle_recvfrom(&request, sock, args, restrict_oob, is_nonblock);
}
SocketCall::RecvMsg => {
return handle_recvmsg(&request, sock, sock_opts, args);
}
SocketCall::RecvMmsg => {
return handle_recvmmsg(&request, sock, sock_opts, args);
}
SocketCall::RecvMmsg64 => {
return handle_recvmmsg64(&request, sock, sock_opts, args);
}
SocketCall::GetSockOpt => {
return handle_getsockopt(&request, sock, args, randomize_fds);
}
SocketCall::SendMsg => {
return handle_sendmsg(&request, sock, sock_opts, args);
}
SocketCall::SendMmsg => {
return handle_sendmmsg(&request, sock, sock_opts, args);
}
_ => {} }
let idx = if subcall == SocketCall::SendTo { 4 } else { 1 };
let addr_remote = args[idx];
if subcall == SocketCall::SendTo && addr_remote == 0 {
return handle_sendto(&request, sock, sock_opts, None, args);
}
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let addr_len = {
let len = args[idx + 1] as libc::c_int;
if len < 0 {
return Err(Errno::EINVAL);
}
len as libc::socklen_t
};
if addr_len == 0 {
if subcall == SocketCall::SendTo {
if addr_remote != 0 {
let name_check = match (sock.get_dom()?, sock.get_stype()?) {
(AddressFamily::Inet6, SockType::Raw) => true,
(AddressFamily::Inet, SockType::Datagram) => matches!(
sock.get_ipproto()?,
Some(IpProto::Udp | IpProto::UdpLite | IpProto::Icmp4)
),
(AddressFamily::Inet6, SockType::Datagram) => matches!(
sock.get_ipproto()?,
Some(IpProto::Udp | IpProto::UdpLite | IpProto::Icmp6)
),
(
AddressFamily::Inet | AddressFamily::Inet6,
SockType::Stream | SockType::SeqPacket,
) => sock.get_ipproto()? == Some(IpProto::Sctp),
_ => false,
};
if name_check {
return Err(Errno::EINVAL);
}
}
return handle_sendto(&request, sock, sock_opts, None, args);
} else {
return Err(Errno::EINVAL);
}
} else if addr_remote == 0 {
return if addr_len as usize > size_of::<libc::sockaddr_storage>() {
Err(Errno::EINVAL)
} else {
Err(Errno::EFAULT)
};
}
let (send_len, fastopen) = if subcall == SocketCall::SendTo {
(
usize::try_from(args[2]).or(Err(Errno::EINVAL))?,
to_msgflags(args[3]).contains(MsgFlags::MSG_FASTOPEN),
)
} else {
(0, false)
};
let addr_buf = request.read_addr(addr_remote, addr_len)?;
let addr_arg =
match parse_addr(subcall, &mut sock, sock_opts, addr_buf, send_len, fastopen)? {
Some(addr_arg) => addr_arg,
None => return handle_sendto(&request, sock, sock_opts, None, args),
};
if subcall == SocketCall::SendTo {
sock.check_send_len(send_len)?;
}
if subcall == SocketCall::Bind
&& addr_arg
.as_sockaddr_in6()
.is_some_and(|sa6| sa6.ip().is_multicast())
&& sock.get_dom()? == AddressFamily::Inet6
&& sock.get_stype()? == SockType::Stream
{
return Err(Errno::EINVAL);
}
let sandbox = request.get_sandbox();
let anyaddr = sandbox.flags.allow_unsafe_any_addr();
let local_net = sandbox.flags.force_local_net();
let (mut addr, root) = canon_addr(&request, &sandbox, &addr_arg, cap)?;
let mut ip_proto = None;
let target = match addr_family(&addr) {
PF_UNIX => {
sandbox_addr(&request, &sandbox, subcall, &addr, &root, cap, ip_proto)?;
match addr_arg.as_unix_addr() {
Some(unix) if unix.path().is_some() => {
#[expect(clippy::disallowed_methods)]
NetAddr::UnixPath(root.unwrap())
}
Some(unix) => match unix.as_abstract() {
Some(name) => {
let mut path = XPathBuf::try_from("@")?;
path.try_append_bytes(name)?;
NetAddr::Unix(path)
}
None => NetAddr::UnixUnnamed,
},
None => {
unreachable!("BUG: AF_UNIX bind without a UNIX address, report a bug!")
}
}
}
PF_INET => {
if !anyaddr {
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);
}
}
ip_proto = sock.get_ipproto()?;
let priv_port = subcall == SocketCall::Bind
&& ip_proto.is_some_and(IpProto::has_port)
&& has_privileged_port_v4(&addr);
sandbox_addr(&request, &sandbox, subcall, &addr, &root, cap, ip_proto)
.map_err(|errno| if priv_port { Errno::EACCES } else { errno })?;
if priv_port && !capable(Capabilities::CAP_NET_BIND_SERVICE) {
return Err(Errno::EACCES);
}
match addr.as_sockaddr_in() {
Some(sin) => NetAddr::Inet(IpAddr::V4(sin.ip()), sin.port()),
None => unreachable!("BUG: AF_INET addr without sockaddr_in, report a bug!"),
}
}
PF_INET6 => {
if !anyaddr {
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'");
}
}
ip_proto = sock.get_ipproto()?;
let priv_port = subcall == SocketCall::Bind
&& ip_proto.is_some_and(IpProto::has_port)
&& has_privileged_port_v6(&addr);
sandbox_addr(&request, &sandbox, subcall, &addr, &root, cap, ip_proto)
.map_err(|errno| if priv_port { Errno::EACCES } else { errno })?;
if priv_port && !capable(Capabilities::CAP_NET_BIND_SERVICE) {
return Err(Errno::EACCES);
}
match addr.as_sockaddr_in6() {
Some(sa6) => NetAddr::Inet(sa6.ip().to_canonical(), sa6.port()),
None => unreachable!("BUG: AF_INET6 addr without sockaddr_in6, report a bug!"),
}
}
PF_UNSPEC => {
NetAddr::None
}
PF_NETLINK => {
NetAddr::None
}
PF_ALG => {
if sock.get_dom()? != AddressFamily::Alg {
return Err(if subcall == SocketCall::SendTo {
sock.get_send_errno()?
} else {
Errno::EAFNOSUPPORT
});
}
if allow_unsafe_kcapi && subcall == SocketCall::Bind {
NetAddr::None
} else {
return Err(Errno::EOPNOTSUPP);
}
}
n if n >= PF_MAX => {
return Err(if subcall == SocketCall::SendTo {
sock.get_send_errno()?
} else {
Errno::EAFNOSUPPORT
})
}
_ if !allow_unsupp_socket => {
return Err(if subcall == SocketCall::SendTo {
sock.get_send_errno()?
} else {
Errno::EAFNOSUPPORT
})
}
_ => NetAddr::None, };
drop(sandbox);
match subcall {
SocketCall::Bind => handle_bind(
&request,
sock,
(addr, addr_arg),
(target, ip_proto),
allow_safe_bind,
),
SocketCall::Connect => handle_connect(
&request,
sock,
(addr, addr_arg),
(target, ip_proto),
allow_safe_bind,
is_nonblock,
),
SocketCall::SendTo => {
handle_sendto(&request, sock, sock_opts, Some((addr, addr_arg)), args)
}
_ => unreachable!(),
}
})
}
#[expect(clippy::cognitive_complexity)]
fn parse_addr(
subcall: SocketCall,
sock: &mut SockInfo,
sock_opts: SockOpts,
mut addr_buf: Vec<u8>,
send_len: usize,
fastopen: bool,
) -> Result<Option<SockaddrStorage>, Errno> {
let SockOpts { is_nonblock, .. } = sock_opts;
if send_len > 0xFFFF
&& sock.get_dom()? == AddressFamily::Inet
&& matches!(sock.get_stype()?, SockType::Datagram | SockType::Raw)
{
return Err(Errno::EMSGSIZE);
}
let addr_len = addr_buf.len();
#[expect(clippy::cast_possible_truncation)]
let mut addr_fam = if addr_len >= size_of::<libc::sa_family_t>() {
u16::from_ne_bytes([addr_buf[0], addr_buf[1]])
} else {
libc::AF_UNSPEC as u16
};
#[expect(clippy::cast_possible_truncation)]
if subcall == SocketCall::Bind
&& sock.get_dom()? == AddressFamily::Inet
&& sock.get_stype()? == SockType::Raw
&& addr_len >= size_of::<libc::sockaddr_in>()
{
addr_fam = libc::AF_INET as u16;
addr_buf[..2].copy_from_slice(&addr_fam.to_ne_bytes());
}
#[expect(clippy::cast_possible_truncation)]
if sock.get_dom()? == AddressFamily::Inet
&& addr_len >= size_of::<libc::sockaddr_in>()
&& addr_fam == libc::AF_UNSPEC as u16
&& match subcall {
SocketCall::Bind => {
addr_buf[4..8] == [0, 0, 0, 0] || sock.get_stype()? == SockType::Raw
}
SocketCall::SendTo | SocketCall::SendMsg | SocketCall::SendMmsg => {
sock.get_stype()? == SockType::Raw
|| matches!(sock.get_ipproto()?, Some(IpProto::Udp | IpProto::UdpLite))
}
_ => false,
}
{
addr_fam = libc::AF_INET as u16;
addr_buf[..2].copy_from_slice(&addr_fam.to_ne_bytes());
}
#[expect(clippy::cast_possible_truncation)]
if sock.get_dom()? == AddressFamily::Inet6
&& addr_len >= SIN6_LEN_RFC2133
&& addr_fam == libc::AF_UNSPEC as u16
&& matches!(
subcall,
SocketCall::SendTo | SocketCall::SendMsg | SocketCall::SendMmsg
)
&& sock.get_stype()? == SockType::Raw
{
addr_fam = libc::AF_INET6 as u16;
addr_buf[..2].copy_from_slice(&addr_fam.to_ne_bytes());
}
if check_sock_state(subcall, sock, addr_fam, fastopen, is_nonblock)? {
return Ok(None);
}
#[expect(clippy::cast_possible_truncation)]
if addr_len >= size_of::<libc::sa_family_t>() {
if addr_fam != libc::AF_UNSPEC as u16
|| subcall == SocketCall::Bind
|| (matches!(sock.get_dom()?, AddressFamily::Inet | AddressFamily::Unix)
&& matches!(
subcall,
SocketCall::SendTo | SocketCall::SendMsg | SocketCall::SendMmsg
))
|| (sock.get_dom()? == AddressFamily::Inet6
&& matches!(
subcall,
SocketCall::SendTo | SocketCall::SendMsg | SocketCall::SendMmsg
)
&& sock.get_stype()? == SockType::Raw)
{
let min_len = min_addr_len(subcall, sock, addr_fam)?;
if addr_len < min_len {
return Err(Errno::EINVAL);
}
}
if sock.get_dom()? == AddressFamily::Netlink
&& addr_fam != libc::AF_NETLINK as u16
&& addr_fam != libc::AF_UNSPEC as u16
{
return Err(Errno::EINVAL);
}
if addr_fam == libc::AF_UNIX as u16 && sock.get_dom()? != AddressFamily::Unix {
return Err(Errno::EAFNOSUPPORT);
}
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::disallowed_methods)]
if addr_len == size_of::<libc::sa_family_t>() {
if subcall == SocketCall::Bind && addr_fam == libc::AF_UNIX as u16 {
let addr = UnixAddr::new_unnamed();
return Ok(Some(
unsafe { SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(addr.len())) }
.unwrap(),
));
}
if addr_fam != libc::AF_UNSPEC as u16 {
return Err(Errno::EINVAL);
}
}
#[expect(clippy::cast_possible_truncation)]
if addr_fam == libc::AF_UNIX as u16 && addr_len > size_of::<libc::sockaddr_un>() {
return Err(Errno::EINVAL);
}
#[expect(clippy::cast_possible_truncation)]
if sock.get_dom()? == AddressFamily::Inet6
&& addr_len < size_of::<libc::sockaddr_in6>()
&& (subcall == SocketCall::Bind || addr_fam == libc::AF_INET6 as u16)
{
let proto = sock.get_ipproto()?;
if (proto == Some(IpProto::Sctp)
&& matches!(
subcall,
SocketCall::Bind
| SocketCall::Connect
| SocketCall::SendTo
| SocketCall::SendMsg
| SocketCall::SendMmsg
))
|| (proto == Some(IpProto::Icmp6)
&& sock.get_stype()? == SockType::Datagram
&& matches!(
subcall,
SocketCall::Bind
| SocketCall::SendTo
| SocketCall::SendMsg
| SocketCall::SendMmsg
))
{
return Err(Errno::EINVAL);
}
}
if subcall == SocketCall::Bind && sock.get_dom()? == AddressFamily::Alg {
if let Some(last) = addr_buf.last_mut() {
*last = 0;
}
}
let addr = addr_buf.as_ptr().cast();
let addr_len = addr_buf.len().try_into().or(Err(Errno::EINVAL))?;
match unsafe { SockaddrStorage::from_raw(addr, Some(addr_len)) } {
Some(addr)
if addr.as_sockaddr_in().is_some()
&& (addr_len as usize) < size_of::<libc::sockaddr_in>() =>
{
Err(Errno::EINVAL)
}
Some(addr)
if addr.as_sockaddr_in6().is_some() && (addr_len as usize) < SIN6_LEN_RFC2133 =>
{
if sock.get_dom()? == AddressFamily::Inet {
Err(Errno::EAFNOSUPPORT)
} else {
Err(Errno::EINVAL)
}
}
Some(mut addr) => {
if (addr_len as usize) < size_of::<libc::sockaddr_in6>() {
clear_scope6(&mut addr);
}
Ok(Some(addr))
}
None => Err(Errno::EINVAL),
}
}
fn canon_addr(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
addr: &SockaddrStorage,
cap: Capability,
) -> Result<(SockaddrStorage, Option<CanonicalPath>), Errno> {
#[expect(clippy::cast_possible_truncation)]
if let Some(path) = addr.as_unix_addr().and_then(|a| a.path()) {
if sandbox.is_chroot() {
return Err(Errno::ENOENT);
}
let path = path.as_os_str().as_bytes();
let null = memchr::memchr(0, path).unwrap_or(path.len());
let path = XPathBuf::try_from(&path[..null])?;
let fsflags = if cap == Capability::CAP_NET_BIND {
FsFlags::MISS_LAST
} else {
FsFlags::MUST_PATH
};
let pid = request.scmpreq.pid();
let path = match safe_canonicalize(
pid,
None,
&path,
fsflags,
Some(request),
Some(sandbox.deref()),
) {
Ok(path) => path,
Err(Errno::EEXIST) if cap == Capability::CAP_NET_BIND => return Err(Errno::EADDRINUSE),
Err(errno) => return Err(errno),
};
if fsflags.missing() && path.typ.is_some() {
return Err(Errno::EADDRINUSE);
}
let sun_path = if path.base().is_empty() {
let mut pfd = XPathBuf::try_from("/proc/thread-self/fd")?;
pfd.try_push_fd(path.dir().as_raw_fd())?;
pfd.try_append_byte(0)?;
pfd
} else {
let mut base = XPathBuf::new();
base.try_append_bytes(path.base().as_os_str().as_bytes())?;
if base.as_bytes().len() < UNIX_PATH_MAX {
base.try_append_byte(0)?;
}
base
};
let mut sockaddr = libc::sockaddr_un {
sun_family: libc::AF_UNIX as libc::sa_family_t,
sun_path: [0; UNIX_PATH_MAX],
};
let socklen = sun_path.len();
if socklen > UNIX_PATH_MAX {
return Err(Errno::ENAMETOOLONG);
}
unsafe {
std::ptr::copy_nonoverlapping(
sun_path.as_ptr(),
sockaddr.sun_path.as_mut_ptr().cast(),
socklen,
)
};
#[expect(clippy::arithmetic_side_effects)]
let size = size_of::<libc::sa_family_t>() + socklen;
let addr = unsafe {
SockaddrStorage::from_raw(
std::ptr::addr_of!(sockaddr) as *const _,
Some(size as libc::socklen_t),
)
}
.ok_or(Errno::EINVAL)?;
Ok((addr, Some(path)))
} else {
Ok((*addr, None))
}
}
pub(crate) fn check_send_state(
request: &UNotifyEventRequest,
sock: &mut SockInfo,
msgflags: MsgFlags,
has_addr: bool,
is_nonblock: bool,
) -> Result<(), Errno> {
if !matches!(sock.get_dom()?, AddressFamily::Inet | AddressFamily::Inet6) {
return Ok(());
}
if !sock.get_stream_send()? {
return if has_addr || is_connected(sock.fd()) {
Ok(())
} else {
Err(Errno::EDESTADDRREQ)
};
}
if msgflags.contains(MsgFlags::MSG_FASTOPEN) {
return Ok(());
}
match safe_getsocktcpstate(sock.as_fd())? {
TcpState::Established | TcpState::CloseWait | TcpState::SynRecv => return Ok(()),
TcpState::SynSent => {
return if is_nonblock || msgflags.contains(MsgFlags::MSG_DONTWAIT) {
Err(Errno::EAGAIN)
} else {
Ok(())
};
}
_ => {}
}
if !msgflags.contains(MsgFlags::MSG_NOSIGNAL) && sock.get_send_sigpipe()? {
request.pidfd_kill(SIGPIPE)?;
}
Err(Errno::EPIPE)
}
fn check_sock_state(
subcall: SocketCall,
sock: &mut SockInfo,
addr_fam: u16,
fastopen: bool,
is_nonblock: bool,
) -> Result<bool, Errno> {
let is_send = matches!(
subcall,
SocketCall::SendTo | SocketCall::SendMsg | SocketCall::SendMmsg
);
match sock.get_dom()? {
AddressFamily::Inet | AddressFamily::Inet6 if is_send => Ok(!fastopen
&& matches!(sock.get_ipproto()?, Some(IpProto::Tcp | IpProto::Mptcp))
&& sock.get_stype()? == SockType::Stream
&& !(safe_getsocktcpstate(sock.as_fd()) == Ok(TcpState::SynSent)
&& safe_getsocktfoconnect(sock.as_fd())?)),
AddressFamily::Inet | AddressFamily::Inet6 if subcall == SocketCall::Connect => {
#[expect(clippy::cast_possible_truncation)]
if addr_fam == libc::AF_UNSPEC as u16
|| !matches!(sock.get_ipproto()?, Some(IpProto::Tcp | IpProto::Mptcp))
{
return Ok(false);
}
match safe_getsocktcpstate(sock.as_fd()) {
Ok(
TcpState::CloseWait
| TcpState::Closing
| TcpState::Established
| TcpState::FinWait1
| TcpState::FinWait2
| TcpState::LastAck
| TcpState::Listen
| TcpState::TimeWait,
) => Err(Errno::EISCONN),
Ok(TcpState::SynSent) if is_nonblock && safe_getsocktfoconnect(sock.as_fd())? => {
Err(Errno::EISCONN)
}
Ok(TcpState::SynSent | TcpState::SynRecv) if is_nonblock => Err(Errno::EALREADY),
_ => Ok(false), }
}
AddressFamily::Unix if is_send => match sock.get_stype()? {
SockType::Stream if is_connected(sock.as_fd()) => Err(Errno::EISCONN),
SockType::Stream => Err(Errno::EOPNOTSUPP),
SockType::SeqPacket if is_connected(sock.as_fd()) => Ok(true),
SockType::SeqPacket => Err(Errno::ENOTCONN),
_ => Ok(false),
},
_ => Ok(false),
}
}
pub(crate) fn is_connected<Fd: AsFd>(fd: Fd) -> bool {
safe_getpeername::<SockaddrStorage>(fd.as_fd().as_raw_fd()).is_ok()
}
pub(crate) fn max_send_len() -> usize {
PROC_INFO.sndbuf_max.min(*MAX_RW_COUNT)
}
pub(crate) fn sandbox_addr(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
subcall: SocketCall,
addr: &SockaddrStorage,
root: &Option<CanonicalPath>,
caps: Capability,
proto: Option<IpProto>,
) -> Result<(), Errno> {
assert!(
matches!(
subcall,
SocketCall::Bind
| SocketCall::Connect
| SocketCall::Accept
| SocketCall::SendTo
| SocketCall::SendMsg
| SocketCall::Accept4
| SocketCall::SendMmsg
),
"BUG: sandbox_addr called with invalid socket subcall:{subcall:#x}, report a bug!"
);
match sockaddr_family(addr) {
AddressFamily::Unix => sandbox_addr_unix(request, sandbox, subcall, addr, root, caps),
AddressFamily::Inet | AddressFamily::Inet6 => {
sandbox_addr_inet(request, sandbox, subcall, addr, caps, proto)
}
_ => sandbox_addr_notsup(sandbox),
}
}
#[expect(clippy::cognitive_complexity)]
pub(crate) fn sandbox_addr_unix(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
subcall: SocketCall,
addr: &SockaddrStorage,
root: &Option<CanonicalPath>,
caps: Capability,
) -> Result<(), Errno> {
assert!(
matches!(
subcall,
SocketCall::Bind
| SocketCall::Connect
| SocketCall::SendTo
| SocketCall::SendMsg
| SocketCall::SendMmsg
),
"BUG: sandbox_addr_unix called with invalid socket subcall:{subcall:#x}, report a bug!"
);
if sandbox.getcaps(caps).is_empty() {
return Ok(());
}
let addr = addr.as_unix_addr().ok_or(Errno::EINVAL)?;
let (path, abs) = match (addr.path(), addr.as_abstract()) {
(Some(path), _) => match root {
Some(path) => (XPathCow::Borrowed(path.abs()), false),
None => {
if sandbox.is_chroot() {
return Err(Errno::ENOENT);
}
let path = path.as_os_str().as_bytes();
let null = memchr::memchr(0, path).unwrap_or(path.len());
let p = XPathBuf::try_from(&path[..null])?;
(XPathCow::Owned(p), false)
}
},
(_, Some(path)) => {
let mut unix = XPathBuf::try_from("@")?;
unix.try_append_bytes(path)?;
(XPathCow::Owned(unix), true)
}
_ => {
(XPathCow::Borrowed(XPath::from_bytes(b"!unnamed")), true)
}
};
let action = sandbox.check_name(caps, &path);
if action.is_logging() && log_enabled!(LogLevel::Warn) {
let sys = subcall.name();
if sandbox.log_scmp() {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "unix": &path, "abs": abs,
"tip": xfmt!("configure `allow/{caps}+{path}'"),
"req": request);
} else {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "unix": &path, "abs": abs,
"tip": xfmt!("configure `allow/{caps}+{path}'"),
"pid": request.scmpreq.pid);
}
}
match action {
Action::Allow | Action::Warn => Ok(()),
Action::Deny | Action::Filter => Err(subcall.into()),
Action::Panic => panic!(),
Action::Exit => safe_exit_group(Errno::from(subcall) as i32),
action => {
let _ = request.kill(action);
Err(subcall.into())
}
}
}
#[expect(clippy::cognitive_complexity)]
pub(crate) fn sandbox_addr_inet(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
subcall: SocketCall,
addr: &SockaddrStorage,
caps: Capability,
proto: Option<IpProto>,
) -> Result<(), Errno> {
if matches!(subcall, SocketCall::Accept | SocketCall::Accept4) {
assert!(
caps.is_empty(),
"BUG: sandbox_addr_inet called with socket subcall:{subcall:#x} and {caps}, report a bug!"
);
} else if matches!(
subcall,
SocketCall::Bind
| SocketCall::Connect
| SocketCall::SendTo
| SocketCall::SendMsg
| SocketCall::SendMmsg
) {
assert!(
!caps.is_empty(),
"BUG: sandbox_addr_inet called with socket subcall:{subcall:#x} and without caps, report a bug!"
);
} else {
unreachable!(
"BUG: sandbox_addr_inet called with socket subcall:{subcall:#x}, report a bug!"
);
}
let caps = if !caps.is_empty() && sandbox.getcaps(caps).is_empty() {
if caps.is_block() {
Capability::empty()
} else {
return Ok(());
}
} else {
caps
};
let (addr, port) = if let Some(sin) = addr.as_sockaddr_in() {
(IpAddr::V4(sin.ip()), sin.port())
} else if let Some(sa6) = addr.as_sockaddr_in6() {
(sa6.ip().to_canonical(), sa6.port())
} else {
return Err(Errno::EINVAL);
};
let action = sandbox.check_ip(caps, addr, port, proto);
if action.is_logging() && log_enabled!(LogLevel::Warn) {
let ipv = if addr.is_ipv6() { 6 } else { 4 };
let sys = subcall.name();
if caps.is_empty() {
if sandbox.log_scmp() {
warn!("ctx": "block", "act": action,
"sys": sys, "addr": xfmt!("{addr}!{port}"),
"ipv": ipv, "proto": proto,
"tip": xfmt!("configure `block-{addr}'"),
"req": request);
} else {
warn!("ctx": "block", "act": action,
"sys": sys, "addr": xfmt!("{addr}!{port}"),
"ipv": ipv, "proto": proto,
"tip": xfmt!("configure `block-{addr}'"),
"pid": request.scmpreq.pid);
}
} else {
let rule = match proto {
Some(proto) => xfmt!("{proto}!{addr}!{port}"),
None => xfmt!("{addr}!{port}"),
};
if sandbox.log_scmp() {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "addr": &rule, "ipv": ipv, "proto": proto,
"tip": xfmt!("configure `allow/{caps}+{rule}'"),
"req": request);
} else {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "addr": &rule, "ipv": ipv, "proto": proto,
"tip": xfmt!("configure `allow/{caps}+{rule}'"),
"pid": request.scmpreq.pid);
}
}
}
match action {
Action::Allow | Action::Warn => Ok(()),
Action::Deny | Action::Filter => Err(subcall.into()),
Action::Panic => panic!(),
Action::Exit => safe_exit_group(Errno::from(subcall) as i32),
action => {
let _ = request.kill(action);
Err(subcall.into())
}
}
}
pub(crate) fn sandbox_addr_notsup(sandbox: &SandboxGuard) -> Result<(), Errno> {
if sandbox.options.allow_unsupp_socket() {
Ok(())
} else {
Err(Errno::EAFNOSUPPORT)
}
}
#[expect(clippy::cognitive_complexity)]
pub(crate) fn sandbox_addr_unnamed(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
subcall: SocketCall,
) -> Result<(), Errno> {
assert!(
matches!(
subcall,
SocketCall::SocketPair
| SocketCall::Connect
| SocketCall::SendTo
| SocketCall::SendMsg
| SocketCall::SendMmsg
),
"BUG: sandbox_addr_unnamed called with invalid socket subcall:{subcall:#x}, report a bug!"
);
let caps = Capability::CAP_NET_BIND;
let name = XPath::from_bytes(b"!unnamed");
if sandbox.getcaps(caps).is_empty() {
return Ok(());
}
let action = sandbox.check_name(caps, name);
if action.is_logging() && log_enabled!(LogLevel::Warn) {
let sys = subcall.name();
if sandbox.log_scmp() {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "unix": &name, "abs": true,
"tip": xfmt!("configure `allow/{caps}+{name}'"),
"req": request);
} else {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "unix": &name, "abs": true,
"tip": xfmt!("configure `allow/{caps}+{name}'"),
"pid": request.scmpreq.pid);
}
}
match action {
Action::Allow | Action::Warn => Ok(()),
Action::Deny | Action::Filter => Err(subcall.into()),
Action::Panic => panic!(),
Action::Exit => safe_exit_group(Errno::from(subcall) as i32),
action => {
let _ = request.kill(action);
Err(subcall.into())
}
}
}
pub(crate) fn handle_safe_bind<Fd: AsFd>(
request: &UNotifyEventRequest,
fd: Fd,
) -> Result<(), Errno> {
let addr: SockaddrStorage = safe_getsockname(fd.as_fd().as_raw_fd())?;
let (addr, port) = if let Some(addr) = addr.as_sockaddr_in() {
let port = addr.port();
if port == 0 {
return Ok(());
}
let addr = IpNet::new_assert(IpAddr::V4(addr.ip()), 32);
(addr, port)
} else if let Some(addr) = addr.as_sockaddr_in6() {
let port = addr.port();
if port == 0 {
return Ok(());
}
let addr = addr.ip();
let addr = if let Some(addr) = addr.to_ipv4_mapped() {
IpNet::new_assert(IpAddr::V4(addr), 32)
} else {
IpNet::new_assert(IpAddr::V6(addr), 128)
};
(addr, port)
} else {
return Ok(());
};
let addr = AddressPattern {
addr,
port: port.into(),
proto: None,
};
info!("ctx": "connect", "op": "allow_safe_bind",
"sys": "connect", "pid": request.scmpreq.pid().as_raw(), "rule": &addr,
"msg": xfmt!("add rule `allow/net/connect+{addr}' after connect"));
let rule = CidrRule {
act: Action::Allow,
cap: Capability::CAP_NET_CONNECT,
pat: addr,
};
let mut sandbox = request.get_mut_sandbox();
match sandbox.cidr_rules.iter().position(|r| *r == rule) {
Some(0) => {} Some(idx) => {
sandbox.cidr_rules.remove(idx);
sandbox.cidr_rules.push_front(rule)?;
}
None => sandbox.cidr_rules.push_front(rule)?,
}
Ok(())
}
pub(crate) fn handle_safe_bind_send(
request: &UNotifyEventRequest,
subcall: SocketCall,
sock: &mut SockInfo,
addr: &SockaddrStorage,
) -> Result<(), Errno> {
if !matches!(sock.get_dom()?, AddressFamily::Inet | AddressFamily::Inet6) {
return Ok(());
}
if let Ok(port) = get_port(sock.fd()) {
if port != 0 {
handle_safe_bind2(request, subcall, addr, port)?;
}
}
Ok(())
}
pub(crate) fn handle_safe_bind2(
request: &UNotifyEventRequest,
subcall: SocketCall,
addr: &SockaddrStorage,
port: u16,
) -> Result<(), Errno> {
let ip = if let Some(addr) = addr.as_sockaddr_in() {
IpAddr::V4(addr.ip())
} else if let Some(addr) = addr.as_sockaddr_in6() {
IpAddr::V6(addr.ip())
} else {
return Err(Errno::EINVAL);
};
let ip = route_src(ip)?;
let addr = match ip {
IpAddr::V4(_) => IpNet::new_assert(ip, 32),
IpAddr::V6(ip6) => match ip6.to_ipv4_mapped() {
Some(ip4) => IpNet::new_assert(IpAddr::V4(ip4), 32),
None => IpNet::new_assert(ip, 128),
},
};
let addr = AddressPattern {
addr,
port: port.into(),
proto: None,
};
info!("ctx": "connect", "op": "allow_safe_bind",
"sys": subcall, "pid": request.scmpreq.pid().as_raw(), "rule": &addr,
"msg": xfmt!("add rule `allow/net/connect+{addr}' after {subcall}"));
let rule = CidrRule {
act: Action::Allow,
cap: Capability::CAP_NET_CONNECT,
pat: addr,
};
let mut sandbox = request.get_mut_sandbox();
match sandbox.cidr_rules.iter().position(|r| *r == rule) {
Some(0) => {} Some(idx) => {
sandbox.cidr_rules.remove(idx);
sandbox.cidr_rules.push_front(rule)?;
}
None => sandbox.cidr_rules.push_front(rule)?,
}
Ok(())
}
pub(crate) fn handle_safe_bind3_v4<'a, I>(
request: &UNotifyEventRequest,
port: u16,
dsts: I,
) -> Result<(), Errno>
where
I: Iterator<Item = &'a SockaddrStorage>,
{
let nl = route_open()?;
let mut ip_set = IpRange::<Ipv4Net>::new();
let mut sandbox = request.get_mut_sandbox();
for dst in dsts {
let ip = match dst.as_sockaddr_in() {
Some(addr) => addr.ip(),
None => continue,
};
if ip_set.contains(&ip) {
continue;
}
ip_set.add(Ipv4Net::new_assert(ip, 32));
let src_ip = match route_query_src(&nl, IpAddr::V4(ip)) {
Ok(ip) => ip,
Err(_) => continue,
};
let addr = IpNet::new_assert(src_ip, 32);
let addr = AddressPattern {
addr,
port: port.into(),
proto: None,
};
info!("ctx": "connect", "op": "allow_safe_bind",
"sys": "sendmmsg", "pid": request.scmpreq.pid().as_raw(),
"rule": &addr,
"msg": xfmt!("add rule `allow/net/connect+{addr}' after sendmmsg"));
let rule = CidrRule {
act: Action::Allow,
cap: Capability::CAP_NET_CONNECT,
pat: addr,
};
match sandbox.cidr_rules.iter().position(|r| *r == rule) {
Some(0) => {} Some(idx) => {
sandbox.cidr_rules.remove(idx);
sandbox.cidr_rules.push_front(rule)?;
}
None => sandbox.cidr_rules.push_front(rule)?,
}
}
Ok(())
}
#[expect(clippy::cognitive_complexity)]
pub(crate) fn handle_safe_bind3_v6<'a, I>(
request: &UNotifyEventRequest,
port: u16,
dsts: I,
) -> Result<(), Errno>
where
I: Iterator<Item = &'a SockaddrStorage>,
{
let nl = route_open()?;
let mut ip_set = IpRange::<Ipv6Net>::new();
let mut sandbox = request.get_mut_sandbox();
for dst in dsts {
let ip = match dst.as_sockaddr_in6() {
Some(addr) => addr.ip(),
None => continue,
};
if ip_set.contains(&ip) {
continue;
}
ip_set.add(Ipv6Net::new_assert(ip, 128));
let src_ip = match route_query_src(&nl, IpAddr::V6(ip)) {
Ok(ip) => ip,
Err(_) => continue,
};
let addr = match src_ip {
IpAddr::V4(_) => IpNet::new_assert(src_ip, 32),
IpAddr::V6(ip6) => match ip6.to_ipv4_mapped() {
Some(ip4) => IpNet::new_assert(IpAddr::V4(ip4), 32),
None => IpNet::new_assert(src_ip, 128),
},
};
let addr = AddressPattern {
addr,
port: port.into(),
proto: None,
};
info!("ctx": "connect", "op": "allow_safe_bind",
"sys": "sendmmsg", "pid": request.scmpreq.pid().as_raw(),
"rule": &addr,
"msg": xfmt!("add rule `allow/net/connect+{addr}' after sendmmsg"));
let rule = CidrRule {
act: Action::Allow,
cap: Capability::CAP_NET_CONNECT,
pat: addr,
};
match sandbox.cidr_rules.iter().position(|r| *r == rule) {
Some(0) => {} Some(idx) => {
sandbox.cidr_rules.remove(idx);
sandbox.cidr_rules.push_front(rule)?;
}
None => sandbox.cidr_rules.push_front(rule)?,
}
}
Ok(())
}
pub(crate) fn get_port<Fd: AsFd>(fd: Fd) -> Result<u16, Errno> {
let addr: SockaddrStorage = safe_getsockname(fd.as_fd().as_raw_fd())?;
if let Some(addr) = addr.as_sockaddr_in() {
Ok(addr.port())
} else if let Some(addr) = addr.as_sockaddr_in6() {
Ok(addr.port())
} else {
Err(Errno::EINVAL)
}
}
pub(crate) fn to_msgflags(arg: u64) -> MsgFlags {
#[expect(clippy::cast_possible_truncation)]
MsgFlags::from_bits_retain(arg as libc::c_int)
}
const SIN6_LEN_RFC2133: usize = 24;
const SALG_NEW_LEN: usize = std::mem::offset_of!(libc::sockaddr_alg, salg_name) + 1;
fn min_addr_len(
subcall: SocketCall,
sock: &mut SockInfo,
addr_fam: libc::sa_family_t,
) -> Result<usize, Errno> {
#[expect(clippy::cast_possible_truncation)]
Ok(match sock.get_dom()? {
AddressFamily::Inet => size_of::<libc::sockaddr_in>(),
AddressFamily::Inet6
if addr_fam == libc::AF_INET as libc::sa_family_t && subcall != SocketCall::Bind =>
{
if matches!(sock.get_ipproto()?, Some(IpProto::Tcp | IpProto::Mptcp)) {
SIN6_LEN_RFC2133
} else {
size_of::<libc::sockaddr_in>()
}
}
AddressFamily::Inet6 => SIN6_LEN_RFC2133,
AddressFamily::Unix if addr_fam != libc::AF_UNIX as libc::sa_family_t => {
return Err(Errno::EINVAL)
}
AddressFamily::Netlink => size_of::<libc::sockaddr_nl>(),
AddressFamily::Alg if subcall == SocketCall::Bind => SALG_NEW_LEN,
_ => 0,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_msgflags_zero_1() {
let flags = to_msgflags(0);
assert!(flags.is_empty());
}
#[test]
fn test_to_msgflags_oob_1() {
let flags = to_msgflags(libc::MSG_OOB as u64);
assert!(flags.contains(MsgFlags::MSG_OOB));
}
#[test]
fn test_to_msgflags_peek_1() {
let flags = to_msgflags(libc::MSG_PEEK as u64);
assert!(flags.contains(MsgFlags::MSG_PEEK));
}
#[test]
fn test_to_msgflags_trunc_1() {
let flags = to_msgflags(libc::MSG_TRUNC as u64);
assert!(flags.contains(MsgFlags::MSG_TRUNC));
}
#[test]
fn test_to_msgflags_truncates_high_bits_1() {
let high_bits: u64 = 0x1_0000_0000;
let flags = to_msgflags(high_bits);
assert!(flags.is_empty());
}
#[test]
fn test_to_msgflags_retains_unknown_bits_1() {
let unknown: u64 = 0x8000_0000;
let flags = to_msgflags(unknown);
assert_eq!(flags.bits(), unknown as i32);
}
}