use std::{
borrow::Cow,
net::IpAddr,
ops::Deref,
os::{
fd::{AsRawFd, RawFd},
unix::ffi::OsStrExt,
},
};
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
fcntl::OFlag,
sys::socket::{SockaddrLike, SockaddrStorage, UnixAddr},
NixPath,
};
use crate::{
compat::{
addr_family, getsockdomain, sockaddr_family, AddressFamily, MsgFlags, PF_ALG, PF_INET,
PF_INET6, PF_MAX, PF_NETLINK, PF_UNIX, PF_UNSPEC,
},
config::HOOK_SCKCALLS,
confine::scmp_arch_bits,
fd::fd_status_flags,
ip::{
clear_scope6, has_privileged_port_v4, has_privileged_port_v6, make_lo6addr, make_loaddr,
SocketCall,
},
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},
req::UNotifyEventRequest,
sandbox::{Action, Capability, SandboxGuard},
syslog::LogLevel,
warn,
};
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;
pub(crate) fn sys_socketcall(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let call: u8 = match req.data.args[0].try_into() {
Ok(call) => call,
Err(_) => return request.fail_syscall(Errno::EINVAL),
};
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];
const NARGS: [u8; 21] = [
0, 3, 3, 3, 2, 3, 3, 3, 4, 4, 4, 6, 6, 2, 5, 5, 3, 3, 4, 5, 4,
];
let narg = NARGS[call as usize] as usize;
#[expect(clippy::arithmetic_side_effects)]
let bufsiz = sizeof_ulong * 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)
}
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, args, SocketCall::SocketPair);
}
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);
#[expect(clippy::cast_possible_truncation)]
let fd = request.get_fd(args[0] as RawFd)?;
if fd_status_flags(&fd)?.contains(OFlag::O_PATH) {
return Err(Errno::EBADF);
}
#[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);
}
if file_type(&fd, None, false)? != FileType::Sock {
return Err(Errno::ENOTSOCK);
}
let sock_dom = getsockdomain(&fd).map(AddressFamily::from_raw)?;
match subcall {
SocketCall::Accept | SocketCall::Accept4 => {
return handle_accept(fd, &request, subcall, args);
}
SocketCall::GetSockName => {
return handle_getsockname(fd, &request, args);
}
SocketCall::GetPeerName => {
return handle_getpeername(fd, &request, args);
}
SocketCall::Send => {
return handle_sendto(fd, args, &request, sock_dom, None, restrict_oob);
}
SocketCall::Recv => {
return handle_recv(fd, args, &request, restrict_oob);
}
SocketCall::RecvFrom => {
return handle_recvfrom(fd, args, &request, restrict_oob);
}
SocketCall::RecvMsg => {
return handle_recvmsg(fd, &request, args, flags, options);
}
SocketCall::RecvMmsg => {
return handle_recvmmsg(fd, &request, args, flags, options);
}
SocketCall::RecvMmsg64 => {
return handle_recvmmsg64(fd, &request, args, flags, options);
}
SocketCall::GetSockOpt => {
return handle_getsockopt(fd, &request, args, randomize_fds);
}
SocketCall::SendMsg => {
return handle_sendmsg(fd, &request, sock_dom, args, flags, options);
}
SocketCall::SendMmsg => {
return handle_sendmmsg(fd, &request, sock_dom, args, flags, options);
}
_ => {} }
let idx = if subcall == SocketCall::SendTo { 4 } else { 1 };
let addr_remote = args[idx];
#[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 {
return handle_sendto(fd, args, &request, sock_dom, None, restrict_oob);
} else {
return Err(Errno::EINVAL);
}
} else if addr_remote == 0 {
return Err(Errno::EFAULT);
}
let sandbox = request.get_sandbox();
let anyaddr = sandbox.flags.allow_unsafe_any_addr();
let local_net = sandbox.flags.force_local_net();
let argaddr = get_addr(&request, subcall, sock_dom, addr_remote, addr_len)?;
let (mut addr, root) = canon_addr(&request, &sandbox, &argaddr, cap)?;
match addr_family(&addr) {
PF_UNIX => {
sandbox_addr(&request, &sandbox, subcall, &addr, &root, cap)?;
}
PF_INET => {
if !anyaddr {
make_loaddr(subcall, &mut addr, local_net)?;
}
sandbox_addr(&request, &sandbox, subcall, &addr, &root, cap).map_err(|errno| {
if subcall == SocketCall::Bind && has_privileged_port_v4(&addr) {
Errno::EACCES
} else {
errno
}
})?;
}
PF_INET6 => {
if !anyaddr {
make_lo6addr(subcall, &mut addr, local_net)?;
}
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": format!("{ip}!{port}"), "scope_id": scope_id,
"msg": format!("zeroed sin6_scope_id={scope_id} on {ip}!{port}"),
"tip": "configure `trace/allow_unsafe_ipv6_scope:1'");
}
}
sandbox_addr(&request, &sandbox, subcall, &addr, &root, cap).map_err(|errno| {
if subcall == SocketCall::Bind && has_privileged_port_v6(&addr) {
Errno::EACCES
} else {
errno
}
})?;
}
PF_UNSPEC => {
}
PF_NETLINK => {
}
PF_ALG if allow_unsafe_kcapi && subcall == SocketCall::Bind => {
}
PF_ALG => {
return Err(Errno::EOPNOTSUPP);
}
n if n >= PF_MAX => return Err(Errno::EAFNOSUPPORT),
_ if !allow_unsupp_socket => return Err(Errno::EAFNOSUPPORT),
_ => {} };
drop(sandbox);
match subcall {
SocketCall::Bind => handle_bind(fd, (addr, argaddr), root, &request, allow_safe_bind),
SocketCall::Connect => handle_connect(fd, (addr, argaddr), &request, allow_safe_bind),
SocketCall::SendTo => handle_sendto(
fd,
args,
&request,
sock_dom,
Some((addr, argaddr)),
restrict_oob,
),
_ => unreachable!(),
}
})
}
fn get_addr(
request: &UNotifyEventRequest,
subcall: SocketCall,
sock_dom: AddressFamily,
addr_remote: u64,
addr_len: libc::socklen_t,
) -> Result<SockaddrStorage, Errno> {
let addr_len: usize = addr_len.try_into().or(Err(Errno::EINVAL))?;
if !(1..=size_of::<libc::sockaddr_storage>()).contains(&addr_len) {
return Err(Errno::EINVAL);
}
let buf = request.read_vec_all(addr_remote, addr_len)?;
#[expect(clippy::cast_possible_truncation)]
if buf.len() >= size_of::<libc::sa_family_t>() {
let addr_fam = u16::from_ne_bytes([buf[0], buf[1]]);
if addr_fam == libc::AF_UNIX as u16 && sock_dom != AddressFamily::Unix {
let min_len = match sock_dom {
AddressFamily::Inet => size_of::<libc::sockaddr_in>(),
AddressFamily::Inet6 => size_of::<libc::sockaddr_in6>(),
_ => size_of::<libc::sa_family_t>(),
};
return if addr_len < min_len {
Err(Errno::EINVAL)
} else {
Err(Errno::EAFNOSUPPORT)
};
}
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::disallowed_methods)]
if addr_len == size_of::<libc::sa_family_t>() {
let addr_fam = u16::from_ne_bytes([buf[0], buf[1]]);
if subcall == SocketCall::Bind && addr_fam == libc::AF_UNIX as u16 {
let addr = UnixAddr::new_unnamed();
return Ok(unsafe {
SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(addr.len()))
}
.unwrap());
}
if addr_fam != libc::AF_UNSPEC as u16 {
return Err(Errno::EINVAL);
}
}
let addr = buf.as_ptr().cast();
let addr_len = buf.len().try_into().or(Err(Errno::EINVAL))?;
const SIN6_LEN_RFC2133: usize = 24;
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 =>
{
Err(Errno::EINVAL)
}
Some(addr) => Ok(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::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 = safe_canonicalize(
pid,
None,
&path,
fsflags,
Some(request),
Some(sandbox.deref()),
)?;
if fsflags.missing() && path.typ.is_some() {
return Err(Errno::EADDRINUSE);
}
let sun_path = if path.base().is_empty() {
let mut pfd = XPathBuf::from("/proc/thread-self/fd");
pfd.push_fd(path.dir().as_raw_fd());
pfd.append_byte(0);
pfd
} else {
let mut base = XPathBuf::from("./");
base.append_bytes(path.base().as_os_str().as_bytes());
base.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 sandbox_addr(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
subcall: SocketCall,
addr: &SockaddrStorage,
root: &Option<CanonicalPath>,
caps: Capability,
) -> 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)
}
_ => 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) => (Cow::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::from(&path[..null]);
(Cow::Owned(p), false)
}
},
(_, Some(path)) => {
let mut unix = XPathBuf::from("@");
unix.append_bytes(path);
(Cow::Owned(unix), true)
}
_ => {
(Cow::Borrowed(XPath::from_bytes(b"!unnamed")), true)
}
};
let action = sandbox.check_unix(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": format!("configure `allow/{caps}+{path}'"),
"req": request);
} else {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "unix": &path, "abs": abs,
"tip": format!("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 => std::process::exit(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,
) -> 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!"
);
}
if !caps.is_empty() && sandbox.getcaps(caps).is_empty() {
return Ok(());
}
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);
if caps.is_empty() && action.is_logging() {
let ipv = if addr.is_ipv6() { 6 } else { 4 };
let sys = subcall.name();
if sandbox.log_scmp() {
warn!("ctx": "block", "act": action,
"sys": sys, "addr": format!("{addr}!{port}"), "ipv": ipv,
"tip": format!("configure `block-{addr}'"),
"req": request);
} else {
warn!("ctx": "block", "act": action,
"sys": sys, "addr": format!("{addr}!{port}"), "ipv": ipv,
"tip": format!("configure `block-{addr}'"),
"pid": request.scmpreq.pid);
}
} else if action.is_logging() {
let ipv = if addr.is_ipv6() { 6 } else { 4 };
let sys = subcall.name();
if sandbox.log_scmp() {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "addr": format!("{addr}!{port}"), "ipv": ipv,
"tip": format!("configure `allow/{caps}+{addr}!{port}'"),
"req": request);
} else {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "addr": format!("{addr}!{port}"), "ipv": ipv,
"tip": format!("configure `allow/{caps}+{addr}!{port}'"),
"pid": request.scmpreq.pid);
}
}
match action {
Action::Allow | Action::Warn => Ok(()),
Action::Deny | Action::Filter => Err(subcall.into()),
Action::Panic => panic!(),
Action::Exit => std::process::exit(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_eq!(
subcall,
SocketCall::SocketPair,
"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_unix(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": format!("configure `allow/{caps}+{name}'"),
"req": request);
} else {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": sys, "unix": &name, "abs": true,
"tip": format!("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 => std::process::exit(Errno::from(subcall) as i32),
action => {
let _ = request.kill(action);
Err(subcall.into())
}
}
}
pub(crate) fn to_msgflags(arg: u64) -> MsgFlags {
#[expect(clippy::cast_possible_truncation)]
MsgFlags::from_bits_retain(arg as libc::c_int)
}
#[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);
}
}