use std::{
ffi::CStr,
io::IoSlice,
mem::MaybeUninit,
num::NonZeroUsize,
os::fd::{AsFd, AsRawFd, FromRawFd, RawFd},
ptr,
ptr::NonNull,
sync::LazyLock,
};
use libc::{
accept4, c_char, c_int, c_long, c_uint, c_void, dev_t, gid_t, mode_t, off64_t, off_t, shutdown,
sockaddr, socket, socketpair, socklen_t, syscall, uid_t, SYS_close, SYS_close_range,
SYS_execveat, SYS_faccessat2, SYS_fchdir, SYS_fchmod, SYS_fchmodat, SYS_fchown, SYS_fchownat,
SYS_fgetxattr, SYS_flistxattr, SYS_fremovexattr, SYS_fsetxattr, SYS_getdents64, SYS_linkat,
SYS_lremovexattr, SYS_lsetxattr, SYS_memfd_create, SYS_mkdirat, SYS_mknodat, SYS_openat2,
SYS_pidfd_getfd, SYS_pidfd_open, SYS_pidfd_send_signal, SYS_pipe2, SYS_ptrace, SYS_renameat2,
SYS_symlinkat, SYS_umask, SYS_uname, SYS_unlinkat, SHUT_RD, SHUT_RDWR, SHUT_WR,
};
use nix::{
errno::Errno,
fcntl::{AtFlags, OFlag},
sys::{
mman::{mmap_anonymous, MapFlags, ProtFlags},
socket::{bind, connect, Shutdown, SockFlag, SockaddrLike, SockaddrStorage},
stat::{Mode, SFlag},
},
unistd::{AccessFlags, Gid, Pid, Uid, UnlinkatFlags},
NixPath,
};
use crate::{
compat::{
pack_cmsg_buf, set_vma_anon_name, AddressFamily, Cmsg, FallocateFlags, MFdFlags, MmsgHdr,
MsgFlags, MsgHdr, OpenHow, RecvMsg, RenameFlags, SecretMemFlags, SockType, TimeSpec64,
},
config::HAVE_PIDFD_THREAD,
confine::resolve_syscall,
fd::{SafeOwnedFd, AT_EXECVE_CHECK, PIDFD_THREAD},
path::{empty_argv, empty_envp, empty_path},
proc::proc_tgid,
ptrace::PtraceRequest,
rng::fillrandom,
sealbox::{getpagesize, mprotect_none, mprotect_readonly, mseal},
uts::UtsName,
};
#[cfg(target_pointer_width = "32")]
pub(crate) type Cookie = u32;
#[cfg(target_pointer_width = "64")]
pub(crate) type Cookie = u64;
#[repr(usize)]
#[derive(Debug, Clone, Copy)]
#[expect(missing_docs)]
pub enum CookieIdx {
Accept4Arg4 = 0,
Accept4Arg5,
BindArg3,
BindArg4,
BindArg5,
CloseArg1,
CloseArg2,
CloseArg3,
CloseArg4,
CloseArg5,
CloseRangeArg3,
CloseRangeArg4,
CloseRangeArg5,
ConnectArg3,
ConnectArg4,
ConnectArg5,
ExecveatArg5,
Faccessat2Arg4,
Faccessat2Arg5,
FallocateArg4,
FallocateArg5,
FchdirArg1,
FchdirArg2,
FchdirArg3,
FchdirArg4,
FchdirArg5,
FchmodArg2,
FchmodArg3,
FchmodArg4,
FchmodArg5,
Fchmodat2Arg4,
Fchmodat2Arg5,
FchmodatArg3,
FchmodatArg4,
FchmodatArg5,
FchownArg3,
FchownArg4,
FchownArg5,
FchownatArg5,
FgetxattrArg4,
FgetxattrArg5,
FlistxattrArg3,
FlistxattrArg4,
FlistxattrArg5,
FremovexattrArg2,
FremovexattrArg3,
FremovexattrArg4,
FremovexattrArg5,
FsetxattrArg5,
Ftruncate64Arg3,
Ftruncate64Arg4,
Ftruncate64Arg5,
FtruncateArg2,
FtruncateArg3,
FtruncateArg4,
FtruncateArg5,
Getdents64Arg3,
Getdents64Arg4,
Getdents64Arg5,
LinkatArg5_1, LinkatArg5_2, LremovexattrArg2,
LremovexattrArg3,
LremovexattrArg4,
LremovexattrArg5,
LsetxattrArg5,
MemfdCreateArg2,
MemfdCreateArg3,
MemfdCreateArg4,
MemfdCreateArg5,
MemfdSecretArg1,
MemfdSecretArg2,
MemfdSecretArg3,
MemfdSecretArg4,
MemfdSecretArg5,
MkdiratArg3,
MkdiratArg4,
MkdiratArg5,
MknodatArg4,
MknodatArg5,
Openat2Arg4,
Openat2Arg5,
PidfdGetInfoArg3,
PidfdGetInfoArg4,
PidfdGetInfoArg5,
PidfdGetfdArg3,
PidfdGetfdArg4,
PidfdGetfdArg5,
PidfdOpenArg2,
PidfdOpenArg3,
PidfdOpenArg4,
PidfdOpenArg5,
PidfdSendSignalArg4,
PidfdSendSignalArg5,
Pipe2Arg2,
Pipe2Arg3,
Pipe2Arg4,
Pipe2Arg5,
ProcmapQueryArg3,
ProcmapQueryArg4,
ProcmapQueryArg5,
PtraceArg4,
PtraceArg5,
RecvMmsgArg4,
RecvMmsgArg5,
RecvMsgArg2,
RecvMsgArg3,
RecvMsgArg4,
RecvMsgArg5,
Renameat2Arg5,
SeccompIoctlNotifAddfdArg3,
SeccompIoctlNotifAddfdArg4,
SeccompIoctlNotifAddfdArg5,
SeccompIoctlNotifSendArg3,
SeccompIoctlNotifSendArg4,
SeccompIoctlNotifSendArg5,
SendMmsgArg3,
SendMmsgArg4,
SendMmsgArg5,
SendMsgArg3,
SendMsgArg4,
SendMsgArg5,
Sendfile64Arg4,
Sendfile64Arg5,
SendfileArg4,
SendfileArg5,
ShutdownArg2,
ShutdownArg3,
ShutdownArg4,
ShutdownArg5,
SocketArg3,
SocketArg4,
SocketArg5,
SocketpairArg4,
SocketpairArg5,
SymlinkatArg3,
SymlinkatArg4,
SymlinkatArg5,
Truncate64Arg3,
Truncate64Arg4,
Truncate64Arg5,
TruncateArg2,
TruncateArg3,
TruncateArg4,
TruncateArg5,
UmaskArg1,
UmaskArg2,
UmaskArg3,
UmaskArg4,
UmaskArg5,
UnameArg1,
UnameArg2,
UnameArg3,
UnameArg4,
UnameArg5,
UnlinkatArg3,
UnlinkatArg4,
UnlinkatArg5,
UtimensatArg4,
UtimensatArg5,
}
impl CookieIdx {
pub const COUNT: usize = Self::UtimensatArg5 as usize + 1;
}
pub struct SyscookiePool {
ptr: *const Cookie,
#[expect(dead_code)]
map_ptr: NonNull<c_void>,
#[expect(dead_code)]
map_len: NonZeroUsize,
}
unsafe impl Sync for SyscookiePool {}
unsafe impl Send for SyscookiePool {}
const VMA_NAME: &CStr = c" Syd: cookie/pool";
impl SyscookiePool {
fn new() -> Result<Self, Errno> {
let page = getpagesize()?;
let cookie_size = size_of::<Cookie>();
let data_size = cookie_size
.checked_mul(CookieIdx::COUNT)
.ok_or(Errno::EINVAL)?;
let data_pages = data_size
.checked_next_multiple_of(page)
.ok_or(Errno::EINVAL)?;
let total_size = page
.checked_add(data_pages)
.and_then(|s| s.checked_add(page))
.ok_or(Errno::EINVAL)?;
let map_len = NonZeroUsize::new(total_size).ok_or(Errno::EINVAL)?;
let map_ptr = unsafe {
mmap_anonymous(
None,
map_len,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_PRIVATE,
)?
};
let data_ptr = unsafe { map_ptr.as_ptr().add(page) };
let data_slice =
unsafe { std::slice::from_raw_parts_mut(data_ptr.cast::<u8>(), data_pages) };
fillrandom(data_slice)?;
let guard_len = NonZeroUsize::new(page).ok_or(Errno::EINVAL)?;
mprotect_none(map_ptr, guard_len)?;
let upper_guard_ptr = unsafe {
NonNull::new_unchecked(map_ptr.as_ptr().add(page).add(data_pages).cast::<c_void>())
};
mprotect_none(upper_guard_ptr, guard_len)?;
let data_region = NonZeroUsize::new(data_pages).ok_or(Errno::EINVAL)?;
let data_region_ptr =
unsafe { NonNull::new_unchecked(map_ptr.as_ptr().add(page).cast::<c_void>()) };
mprotect_readonly(data_region_ptr, data_region)?;
let _ = set_vma_anon_name(data_region_ptr, data_region, Some(VMA_NAME));
match mseal(map_ptr, map_len) {
Ok(_) | Err(Errno::EPERM | Errno::ENOSYS) => {}
Err(errno) => return Err(errno),
}
Ok(SyscookiePool {
ptr: data_ptr.cast::<Cookie>(),
map_ptr,
map_len,
})
}
#[inline(always)]
pub fn get(&self, idx: CookieIdx) -> Cookie {
unsafe { *self.ptr.add(idx as usize) }
}
}
#[expect(clippy::disallowed_methods)]
pub static SYSCOOKIE_POOL: LazyLock<SyscookiePool> =
LazyLock::new(|| SyscookiePool::new().expect("failed to initialize syscall cookie pool"));
#[inline(always)]
pub(crate) fn safe_close(fd: RawFd) -> Result<(), Errno> {
Errno::result(unsafe {
syscall(
SYS_close,
fd,
SYSCOOKIE_POOL.get(CookieIdx::CloseArg1),
SYSCOOKIE_POOL.get(CookieIdx::CloseArg2),
SYSCOOKIE_POOL.get(CookieIdx::CloseArg3),
SYSCOOKIE_POOL.get(CookieIdx::CloseArg4),
SYSCOOKIE_POOL.get(CookieIdx::CloseArg5),
)
})
.map(drop)
}
#[inline(always)]
pub(crate) fn safe_close_range(first: c_uint, last: c_uint, flags: c_uint) -> Result<(), Errno> {
Errno::result(unsafe {
syscall(
SYS_close_range,
first,
last,
flags,
SYSCOOKIE_POOL.get(CookieIdx::CloseRangeArg3),
SYSCOOKIE_POOL.get(CookieIdx::CloseRangeArg4),
SYSCOOKIE_POOL.get(CookieIdx::CloseRangeArg5),
)
})
.map(drop)
}
#[inline(always)]
pub(crate) fn safe_openat2<Fd: AsFd, P: NixPath + ?Sized>(
dirfd: Fd,
path: &P,
mut how: OpenHow,
) -> Result<SafeOwnedFd, Errno> {
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
SYS_openat2,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
ptr::addr_of_mut!(how),
size_of::<OpenHow>(),
SYSCOOKIE_POOL.get(CookieIdx::Openat2Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Openat2Arg5),
)
}
})?;
#[expect(clippy::cast_possible_truncation)]
Errno::result(res).map(|r| unsafe { SafeOwnedFd::from_raw_fd(r as RawFd) })
}
pub static SYS_SOCKET: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("socket"));
#[inline(always)]
pub fn safe_socket(
domain: AddressFamily,
stype: SockType,
flags: SockFlag,
proto: c_int,
) -> Result<SafeOwnedFd, Errno> {
let domain = domain.as_raw();
let stype = stype.as_raw() | flags.bits();
if let Some(sys_socket) = *SYS_SOCKET {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys_socket,
domain,
stype,
proto,
SYSCOOKIE_POOL.get(CookieIdx::SocketArg3),
SYSCOOKIE_POOL.get(CookieIdx::SocketArg4),
SYSCOOKIE_POOL.get(CookieIdx::SocketArg5),
)
})
.map(|fd| fd as RawFd)
} else {
Errno::result(unsafe { socket(domain, stype, proto) })
}
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd) }
})
}
pub static SYS_SOCKETPAIR: LazyLock<Option<c_long>> =
LazyLock::new(|| resolve_syscall("socketpair"));
#[inline(always)]
pub fn safe_socketpair(
domain: AddressFamily,
stype: SockType,
proto: c_int,
flags: SockFlag,
) -> Result<(SafeOwnedFd, SafeOwnedFd), Errno> {
let mut fds = [-1, -1];
let domain = domain.as_raw();
let stype = stype.as_raw() | flags.bits();
if let Some(sys_socketpair) = *SYS_SOCKETPAIR {
Errno::result(unsafe {
syscall(
sys_socketpair,
domain,
stype,
proto,
fds.as_mut_ptr(),
SYSCOOKIE_POOL.get(CookieIdx::SocketpairArg4),
SYSCOOKIE_POOL.get(CookieIdx::SocketpairArg5),
)
})?;
} else {
Errno::result(unsafe { socketpair(domain, stype, proto, fds.as_mut_ptr()) })?;
}
unsafe {
Ok((
SafeOwnedFd::from_raw_fd(fds[0]),
SafeOwnedFd::from_raw_fd(fds[1]),
))
}
}
pub static SYS_ACCEPT4: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("accept4"));
#[derive(Debug)]
pub struct SizedSockaddrStorage {
pub addr: SockaddrStorage,
pub size: socklen_t,
}
#[inline(always)]
pub(crate) fn safe_accept4<Fd: AsFd>(
fd: Fd,
flags: SockFlag,
want_src_addr: bool,
) -> Result<(SafeOwnedFd, Option<SizedSockaddrStorage>), Errno> {
if want_src_addr {
let mut storage = MaybeUninit::<libc::sockaddr_storage>::zeroed();
#[expect(clippy::cast_possible_truncation)]
let mut size = size_of::<libc::sockaddr_storage>() as socklen_t;
let fd = do_accept4(fd, storage.as_mut_ptr().cast(), &raw mut size, flags)?;
let addr = unsafe { SockaddrStorage::from_raw(storage.as_ptr().cast(), Some(size)) }
.ok_or(Errno::EINVAL)?;
let addr = SizedSockaddrStorage { addr, size };
Ok((fd, Some(addr)))
} else {
let fd = do_accept4(fd, ptr::null_mut(), ptr::null_mut(), flags)?;
Ok((fd, None))
}
}
#[inline(always)]
fn do_accept4<Fd: AsFd>(
fd: Fd,
addr: *mut sockaddr,
len: *mut socklen_t,
flags: SockFlag,
) -> Result<SafeOwnedFd, Errno> {
if let Some(sys_accept4) = *SYS_ACCEPT4 {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys_accept4,
fd.as_fd().as_raw_fd(),
addr,
len,
flags.bits(),
SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg5),
)
})
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
} else {
Errno::result(unsafe { accept4(fd.as_fd().as_raw_fd(), addr, len, flags.bits()) }).map(
|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd) }
},
)
}
}
pub(crate) static SYS_BIND: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("bind"));
#[inline(always)]
pub fn safe_bind<Fd: AsFd>(fd: Fd, addr: &dyn SockaddrLike) -> Result<(), Errno> {
if let Some(sys_bind) = *SYS_BIND {
Errno::result(unsafe {
syscall(
sys_bind,
fd.as_fd().as_raw_fd(),
addr.as_ptr(),
addr.len(),
SYSCOOKIE_POOL.get(CookieIdx::BindArg3),
SYSCOOKIE_POOL.get(CookieIdx::BindArg4),
SYSCOOKIE_POOL.get(CookieIdx::BindArg5),
)
})
.map(drop)
} else {
bind(fd.as_fd().as_raw_fd(), addr)
}
}
pub static SYS_CONNECT: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("connect"));
#[inline(always)]
pub fn safe_connect<Fd: AsFd>(fd: Fd, addr: &dyn SockaddrLike) -> Result<(), Errno> {
if let Some(sys_connect) = *SYS_CONNECT {
Errno::result(unsafe {
syscall(
sys_connect,
fd.as_fd().as_raw_fd(),
addr.as_ptr(),
addr.len(),
SYSCOOKIE_POOL.get(CookieIdx::ConnectArg3),
SYSCOOKIE_POOL.get(CookieIdx::ConnectArg4),
SYSCOOKIE_POOL.get(CookieIdx::ConnectArg5),
)
})
.map(drop)
} else {
connect(fd.as_fd().as_raw_fd(), addr)
}
}
pub static SYS_SHUTDOWN: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("shutdown"));
#[inline(always)]
pub fn safe_shutdown<Fd: AsFd>(fd: Fd, how: Shutdown) -> Result<(), Errno> {
let how = match how {
Shutdown::Read => SHUT_RD,
Shutdown::Write => SHUT_WR,
Shutdown::Both => SHUT_RDWR,
};
if let Some(sys_shutdown) = *SYS_SHUTDOWN {
Errno::result(unsafe {
syscall(
sys_shutdown,
fd.as_fd().as_raw_fd(),
how,
SYSCOOKIE_POOL.get(CookieIdx::ShutdownArg2),
SYSCOOKIE_POOL.get(CookieIdx::ShutdownArg3),
SYSCOOKIE_POOL.get(CookieIdx::ShutdownArg4),
SYSCOOKIE_POOL.get(CookieIdx::ShutdownArg5),
)
})
.map(drop)
} else {
Errno::result(unsafe { shutdown(fd.as_fd().as_raw_fd(), how) }).map(drop)
}
}
pub static SYS_SENDMSG: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("sendmsg"));
#[inline(always)]
pub(crate) fn safe_sendmsg<Fd: AsFd, S: SockaddrLike>(
fd: Fd,
iov: &[IoSlice<'_>],
cmsgs: &[Cmsg<'_>],
flags: MsgFlags,
addr: Option<&S>,
) -> Result<usize, Errno> {
let fd = fd.as_fd().as_raw_fd();
let flags = flags.bits();
let mut msg_buf = pack_cmsg_buf(cmsgs)?;
let mut msg_hdr = MsgHdr::default();
if let Some(addr) = addr {
msg_hdr.set_addr(addr);
}
msg_hdr.set_iov(iov);
msg_hdr.set_control(&mut msg_buf);
let msg_hdr = msg_hdr.as_mut_ptr().cast();
if let Some(sys_sendmsg) = *SYS_SENDMSG {
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys_sendmsg,
fd,
msg_hdr,
flags,
SYSCOOKIE_POOL.get(CookieIdx::SendMsgArg3),
SYSCOOKIE_POOL.get(CookieIdx::SendMsgArg4),
SYSCOOKIE_POOL.get(CookieIdx::SendMsgArg5),
)
})
.map(|r| r as usize)
} else {
#[expect(clippy::cast_sign_loss)]
Errno::result(unsafe { libc::sendmsg(fd, msg_hdr, flags) }).map(|r| r as usize)
}
}
pub static SYS_SENDMMSG: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("sendmmsg"));
#[inline(always)]
pub(crate) fn safe_sendmmsg<Fd: AsFd>(
fd: Fd,
msgvec: &mut [MmsgHdr],
flags: MsgFlags,
) -> Result<usize, Errno> {
let fd = fd.as_fd().as_raw_fd();
let flags = flags.bits();
let msglen: c_uint = msgvec.len().try_into().or(Err(Errno::EOVERFLOW))?;
let msgvec = msgvec.as_mut_ptr().cast();
if let Some(sys_sendmmsg) = *SYS_SENDMMSG {
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys_sendmmsg,
fd,
msgvec,
msglen,
flags,
SYSCOOKIE_POOL.get(CookieIdx::SendMmsgArg4),
SYSCOOKIE_POOL.get(CookieIdx::SendMmsgArg5),
)
})
.map(|r| r as usize)
} else {
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::as_underscore)]
Errno::result(unsafe { libc::sendmmsg(fd, msgvec, msglen, flags as _) }).map(|r| r as usize)
}
}
pub static SYS_RECVMSG: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("recvmsg"));
#[inline(always)]
pub(crate) fn safe_recvmsg<'a, Fd: AsFd>(
fd: Fd,
msghdr: &'a mut MsgHdr,
flags: MsgFlags,
) -> Result<RecvMsg<'a>, Errno> {
let fd = fd.as_fd().as_raw_fd();
let flags = flags.bits();
let msgptr = msghdr.as_mut_ptr().cast();
#[expect(clippy::cast_sign_loss)]
let bytes = if let Some(sys_recvmsg) = *SYS_RECVMSG {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys_recvmsg,
fd,
msgptr,
flags,
SYSCOOKIE_POOL.get(CookieIdx::RecvMsgArg3),
SYSCOOKIE_POOL.get(CookieIdx::RecvMsgArg4),
SYSCOOKIE_POOL.get(CookieIdx::RecvMsgArg5),
)
})
.map(|r| r as usize)
} else {
Errno::result(unsafe { libc::recvmsg(fd, msgptr, flags) }).map(|r| r as usize)
}?;
Ok(RecvMsg {
bytes,
msghdr,
flags: msghdr.msg_flags(),
})
}
pub static SYS_RECVMMSG: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("recvmmsg"));
pub static SYS_RECVMMSG_TIME64: LazyLock<Option<c_long>> =
LazyLock::new(|| resolve_syscall("recvmmsg_time64"));
#[inline(always)]
pub(crate) fn safe_recvmmsg<Fd: AsFd>(
fd: Fd,
msgvec: &mut [MmsgHdr],
flags: MsgFlags,
timeout: Option<&mut TimeSpec64>,
) -> Result<usize, Errno> {
let fd = fd.as_fd().as_raw_fd();
let flags = flags.bits();
let msglen: c_uint = msgvec.len().try_into().or(Err(Errno::EOVERFLOW))?;
let msgvec = msgvec.as_mut_ptr().cast();
if let Some(sys) = *SYS_RECVMMSG_TIME64 {
let timeout = match timeout {
Some(timeout) => std::ptr::from_mut(timeout).cast::<c_void>(),
None => std::ptr::null_mut(),
};
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys,
fd,
msgvec,
msglen,
flags,
timeout,
SYSCOOKIE_POOL.get(CookieIdx::RecvMmsgArg5),
)
})
.map(|r| r as usize)
} else if let Some(sys) = *SYS_RECVMMSG {
#[cfg(target_pointer_width = "32")]
{
use crate::compat::TimeSpec32;
let mut timeout32;
let timeout32 = if let Some(timeout) = timeout.as_deref() {
timeout32 = TimeSpec32::try_from(*timeout)?;
&raw mut timeout32 as *mut c_void
} else {
std::ptr::null_mut()
};
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys,
fd,
msgvec,
msglen,
flags,
timeout32,
SYSCOOKIE_POOL.get(CookieIdx::RecvMmsgArg5),
)
})
.map(|r| r as usize)
}
#[cfg(not(target_pointer_width = "32"))]
{
let timeout = match timeout {
Some(timeout) => std::ptr::from_mut(timeout).cast::<c_void>(),
None => std::ptr::null_mut(),
};
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
sys,
fd,
msgvec,
msglen,
flags,
timeout,
SYSCOOKIE_POOL.get(CookieIdx::RecvMmsgArg5),
)
})
.map(|r| r as usize)
}
} else {
let timeout = match timeout {
Some(timeout) => std::ptr::from_mut(timeout).cast::<c_void>(),
None => std::ptr::null_mut(),
};
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::as_underscore)]
Errno::result(unsafe { libc::recvmmsg(fd, msgvec, msglen, flags as _, timeout.cast()) })
.map(|r| r as usize)
}
}
#[inline(always)]
pub fn safe_memfd_create<P: NixPath + ?Sized>(
name: &P,
flags: MFdFlags,
) -> Result<SafeOwnedFd, Errno> {
if name.len() > 249 {
return Err(Errno::EINVAL);
}
let res = name.with_nix_path(|cstr| {
unsafe {
syscall(
SYS_memfd_create,
cstr.as_ptr(),
flags.bits(),
SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg2),
SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg3),
SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg4),
SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg5),
)
}
})?;
#[expect(clippy::cast_possible_truncation)]
Errno::result(res).map(|r| unsafe { SafeOwnedFd::from_raw_fd(r as RawFd) })
}
static SYS_MEMFD_SECRET: LazyLock<Option<c_long>> =
LazyLock::new(|| resolve_syscall("memfd_secret"));
#[inline(always)]
pub fn safe_memfd_secret(flags: SecretMemFlags) -> Result<SafeOwnedFd, Errno> {
let sys_memfd_secret = SYS_MEMFD_SECRET.ok_or(Errno::ENOSYS)?;
Errno::result(unsafe {
syscall(
sys_memfd_secret,
flags.bits(),
SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg1),
SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg2),
SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg3),
SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg4),
SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg5),
)
})
.map(|r| {
#[expect(clippy::cast_possible_truncation)]
unsafe {
SafeOwnedFd::from_raw_fd(r as RawFd)
}
})
}
#[inline(always)]
pub(crate) fn safe_renameat2<Fd1: AsFd, Fd2: AsFd, P1: NixPath + ?Sized, P2: NixPath + ?Sized>(
old_dirfd: Fd1,
old_path: &P1,
new_dirfd: Fd2,
new_path: &P2,
flags: RenameFlags,
) -> Result<(), Errno> {
let res = old_path.with_nix_path(|old_cstr| {
new_path.with_nix_path(|new_cstr| {
unsafe {
syscall(
SYS_renameat2,
old_dirfd.as_fd().as_raw_fd(),
old_cstr.as_ptr(),
new_dirfd.as_fd().as_raw_fd(),
new_cstr.as_ptr(),
flags.bits(),
SYSCOOKIE_POOL.get(CookieIdx::Renameat2Arg5),
)
}
})
})??;
Errno::result(res).map(drop)
}
static SYS_FCHMODAT2: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("fchmodat2"));
static SYS_TRUNCATE: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("truncate"));
#[allow(dead_code)]
static SYS_TRUNCATE64: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("truncate64"));
static SYS_FTRUNCATE: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("ftruncate"));
#[allow(dead_code)]
static SYS_FTRUNCATE64: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("ftruncate64"));
static SYS_SENDFILE: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("sendfile"));
pub static SYS_SENDFILE64: LazyLock<Option<c_long>> =
LazyLock::new(|| resolve_syscall("sendfile64"));
pub(crate) fn safe_truncate<P: NixPath + ?Sized>(path: &P, len: off_t) -> Result<(), Errno> {
if size_of::<off_t>() > size_of::<c_long>() {
return safe_truncate64(path, off64_t::from(len));
}
let sys_truncate = SYS_TRUNCATE.ok_or(Errno::ENOSYS)?;
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
sys_truncate,
cstr.as_ptr(),
len,
SYSCOOKIE_POOL.get(CookieIdx::TruncateArg2),
SYSCOOKIE_POOL.get(CookieIdx::TruncateArg3),
SYSCOOKIE_POOL.get(CookieIdx::TruncateArg4),
SYSCOOKIE_POOL.get(CookieIdx::TruncateArg5),
)
}
})?;
Errno::result(res).map(drop)
}
pub(crate) fn safe_truncate64<P: NixPath + ?Sized>(path: &P, len: off64_t) -> Result<(), Errno> {
#[cfg(not(any(
target_pointer_width = "64",
all(target_arch = "x86_64", target_pointer_width = "32"),
target_arch = "x86",
target_arch = "arm",
target_arch = "powerpc",
target_arch = "m68k",
target_arch = "mips",
target_arch = "mips32r6",
)))]
{
compile_error!("BUG: safe_truncate64 is not implemented for this architecture!");
}
#[cfg(any(
target_pointer_width = "64",
all(target_arch = "x86_64", target_pointer_width = "32"),
))]
{
safe_truncate(path, len)
}
#[cfg(any(target_arch = "m68k", target_arch = "x86",))]
{
let sys_truncate64 = SYS_TRUNCATE64.ok_or(Errno::ENOSYS)?;
let val = len as u64;
let low = (val & 0xFFFF_FFFF) as c_long;
let high = (val >> 32) as c_long;
let (a, b) = if cfg!(target_endian = "little") {
(low, high)
} else {
(high, low)
};
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
sys_truncate64,
cstr.as_ptr(),
a,
b,
SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg3),
SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg5),
)
}
})?;
Errno::result(res).map(drop)
}
#[cfg(any(
target_arch = "arm",
target_arch = "powerpc",
target_arch = "mips",
target_arch = "mips32r6"
))]
{
let sys_truncate64 = SYS_TRUNCATE64.ok_or(Errno::ENOSYS)?;
let val = len as u64;
let low = (val & 0xFFFF_FFFF) as c_long;
let high = (val >> 32) as c_long;
let (a, b) = if cfg!(target_endian = "little") {
(low, high)
} else {
(high, low)
};
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
sys_truncate64,
cstr.as_ptr(),
0 as c_long,
a,
b,
SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg5),
)
}
})?;
Errno::result(res).map(drop)
}
}
pub(crate) fn safe_ftruncate<Fd: AsFd>(fd: Fd, len: off_t) -> Result<(), Errno> {
if size_of::<off_t>() > size_of::<c_long>() {
return safe_ftruncate64(fd, off64_t::from(len));
}
let sys_ftruncate = SYS_FTRUNCATE.ok_or(Errno::ENOSYS)?;
Errno::result(unsafe {
syscall(
sys_ftruncate,
fd.as_fd().as_raw_fd(),
len,
SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg2),
SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg3),
SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg4),
SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg5),
)
})
.map(drop)
}
pub(crate) fn safe_ftruncate64<Fd: AsFd>(fd: Fd, len: off64_t) -> Result<(), Errno> {
#[cfg(not(any(
target_pointer_width = "64",
all(target_arch = "x86_64", target_pointer_width = "32"),
target_arch = "x86",
target_arch = "arm",
target_arch = "powerpc",
target_arch = "m68k",
target_arch = "mips",
target_arch = "mips32r6",
)))]
{
compile_error!("BUG: safe_ftruncate64 is not implemented for this architecture!");
}
#[cfg(any(
target_pointer_width = "64",
all(target_arch = "x86_64", target_pointer_width = "32"),
))]
{
safe_ftruncate(fd, len)
}
#[cfg(any(target_arch = "m68k", target_arch = "x86",))]
{
let sys_ftruncate64 = SYS_FTRUNCATE64.ok_or(Errno::ENOSYS)?;
let val = len as u64;
let low = (val & 0xFFFF_FFFF) as c_long;
let high = (val >> 32) as c_long;
let (a, b) = if cfg!(target_endian = "little") {
(low, high)
} else {
(high, low)
};
Errno::result(unsafe {
syscall(
sys_ftruncate64,
fd.as_fd().as_raw_fd(),
a,
b,
SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg3),
SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg5),
)
})
.map(drop)
}
#[cfg(any(
target_arch = "arm",
target_arch = "powerpc",
target_arch = "mips",
target_arch = "mips32r6"
))]
{
let sys_ftruncate64 = SYS_FTRUNCATE64.ok_or(Errno::ENOSYS)?;
let val = len as u64;
let low = (val & 0xFFFF_FFFF) as c_long;
let high = (val >> 32) as c_long;
let (a, b) = if cfg!(target_endian = "little") {
(low, high)
} else {
(high, low)
};
Errno::result(unsafe {
syscall(
sys_ftruncate64,
fd.as_fd().as_raw_fd(),
0 as c_long,
a,
b,
SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg5),
)
})
.map(drop)
}
}
pub(crate) fn safe_fallocate<Fd: AsFd>(
fd: Fd,
mode: FallocateFlags,
offset: off64_t,
len: off64_t,
) -> Result<(), Errno> {
#[cfg(target_pointer_width = "64")]
{
Errno::result(unsafe {
syscall(
libc::SYS_fallocate,
fd.as_fd().as_raw_fd(),
mode.bits(),
offset,
len,
SYSCOOKIE_POOL.get(CookieIdx::FallocateArg4),
SYSCOOKIE_POOL.get(CookieIdx::FallocateArg5),
)
})
.map(drop)
}
#[cfg(target_pointer_width = "32")]
{
crate::fs::fallocate64(fd, mode, offset, len)
}
}
#[inline(always)]
pub(crate) fn safe_unlinkat<Fd: AsFd, P: NixPath + ?Sized>(
dirfd: Fd,
path: &P,
flag: UnlinkatFlags,
) -> Result<(), Errno> {
let atflag = match flag {
UnlinkatFlags::RemoveDir => AtFlags::AT_REMOVEDIR,
UnlinkatFlags::NoRemoveDir => AtFlags::empty(),
};
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
SYS_unlinkat,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
atflag.bits(),
SYSCOOKIE_POOL.get(CookieIdx::UnlinkatArg3),
SYSCOOKIE_POOL.get(CookieIdx::UnlinkatArg4),
SYSCOOKIE_POOL.get(CookieIdx::UnlinkatArg5),
)
}
})?;
Errno::result(res).map(drop)
}
#[inline(always)]
pub(crate) fn safe_linkat<Fd1: AsFd, Fd2: AsFd, P1: NixPath + ?Sized, P2: NixPath + ?Sized>(
olddirfd: Fd1,
oldpath: &P1,
newdirfd: Fd2,
newpath: &P2,
flag: AtFlags,
) -> Result<(), Errno> {
let res = oldpath.with_nix_path(|oldcstr| {
newpath.with_nix_path(|newcstr| {
unsafe {
syscall(
SYS_linkat,
olddirfd.as_fd().as_raw_fd(),
oldcstr.as_ptr(),
newdirfd.as_fd().as_raw_fd(),
newcstr.as_ptr(),
flag.bits(),
SYSCOOKIE_POOL.get(CookieIdx::LinkatArg5_1),
)
}
})
})??;
Errno::result(res).map(drop)
}
#[inline(always)]
pub(crate) fn safe_fdlink<Fd1: AsFd, Fd2: AsFd, P1: NixPath + ?Sized>(
olddirfd: Fd1,
newdirfd: Fd2,
newpath: &P1,
) -> Result<(), Errno> {
let res = newpath.with_nix_path(|newcstr| {
unsafe {
syscall(
SYS_linkat,
olddirfd.as_fd().as_raw_fd(),
empty_path() as *const c_char,
newdirfd.as_fd().as_raw_fd(),
newcstr.as_ptr(),
AtFlags::AT_EMPTY_PATH.bits(),
SYSCOOKIE_POOL.get(CookieIdx::LinkatArg5_2),
)
}
})?;
Errno::result(res).map(drop)
}
#[inline(always)]
pub(crate) fn safe_symlinkat<Fd: AsFd, P1: NixPath + ?Sized, P2: NixPath + ?Sized>(
path1: &P1,
dirfd: Fd,
path2: &P2,
) -> Result<(), Errno> {
let res = path1.with_nix_path(|path1| {
path2.with_nix_path(|path2| {
unsafe {
syscall(
SYS_symlinkat,
path1.as_ptr(),
dirfd.as_fd().as_raw_fd(),
path2.as_ptr(),
SYSCOOKIE_POOL.get(CookieIdx::SymlinkatArg3),
SYSCOOKIE_POOL.get(CookieIdx::SymlinkatArg4),
SYSCOOKIE_POOL.get(CookieIdx::SymlinkatArg5),
)
}
})
})??;
Errno::result(res).map(drop)
}
#[inline(always)]
pub(crate) fn safe_mkdirat<Fd: AsFd, P: NixPath + ?Sized>(
dirfd: Fd,
path: &P,
mode: Mode,
) -> Result<(), Errno> {
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
SYS_mkdirat,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
mode.bits(),
SYSCOOKIE_POOL.get(CookieIdx::MkdiratArg3),
SYSCOOKIE_POOL.get(CookieIdx::MkdiratArg4),
SYSCOOKIE_POOL.get(CookieIdx::MkdiratArg5),
)
}
})?;
Errno::result(res).map(drop)
}
#[inline(always)]
pub(crate) fn safe_mknodat<Fd: AsFd, P: NixPath + ?Sized>(
dirfd: Fd,
path: &P,
kind: SFlag,
perm: Mode,
dev: dev_t,
) -> Result<(), Errno> {
let mode = kind.bits() | perm.bits();
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
SYS_mknodat,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
mode,
(dev & 0xFFFF_FFFF) as libc::c_ulong,
SYSCOOKIE_POOL.get(CookieIdx::MknodatArg4),
SYSCOOKIE_POOL.get(CookieIdx::MknodatArg5),
)
}
})?;
Errno::result(res).map(drop)
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
pub fn safe_getdents64<Fd: AsFd>(fd: Fd, buf: &mut [u8]) -> Result<usize, Errno> {
Errno::result(unsafe {
syscall(
SYS_getdents64,
fd.as_fd().as_raw_fd(),
buf.as_mut_ptr().cast::<c_void>(),
buf.len(),
SYSCOOKIE_POOL.get(CookieIdx::Getdents64Arg3),
SYSCOOKIE_POOL.get(CookieIdx::Getdents64Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Getdents64Arg5),
)
})
.map(|size| size as usize)
}
#[inline(always)]
pub fn safe_fchdir<Fd: AsFd>(dirfd: Fd) -> Result<(), Errno> {
Errno::result(unsafe {
syscall(
SYS_fchdir,
dirfd.as_fd().as_raw_fd(),
SYSCOOKIE_POOL.get(CookieIdx::FchdirArg1),
SYSCOOKIE_POOL.get(CookieIdx::FchdirArg2),
SYSCOOKIE_POOL.get(CookieIdx::FchdirArg3),
SYSCOOKIE_POOL.get(CookieIdx::FchdirArg4),
SYSCOOKIE_POOL.get(CookieIdx::FchdirArg5),
)
})
.map(drop)
}
pub fn safe_faccess<Fd: AsFd>(fd: Fd, mode: AccessFlags, mut flags: AtFlags) -> Result<(), Errno> {
flags.remove(AtFlags::AT_SYMLINK_NOFOLLOW);
flags.insert(AtFlags::AT_EMPTY_PATH);
Errno::result(unsafe {
syscall(
SYS_faccessat2,
fd.as_fd().as_raw_fd(),
empty_path() as *const c_char,
mode.bits(),
flags.bits(),
SYSCOOKIE_POOL.get(CookieIdx::Faccessat2Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Faccessat2Arg5),
)
})
.map(drop)
}
#[inline(always)]
pub fn safe_execve_check<Fd: AsFd>(fd: Fd) -> Result<(), Errno> {
let flags = (AT_EXECVE_CHECK | AtFlags::AT_EMPTY_PATH).bits();
Errno::result(unsafe {
syscall(
SYS_execveat,
fd.as_fd().as_raw_fd(),
empty_path() as *const c_char,
empty_argv() as *const *const c_char,
empty_envp() as *const *const c_char,
flags,
SYSCOOKIE_POOL.get(CookieIdx::ExecveatArg5),
)
})
.map(drop)
}
#[inline(always)]
pub(crate) fn safe_fchmodat<Fd: AsFd, P: NixPath + ?Sized>(
dirfd: Fd,
path: &P,
mode: Mode,
) -> Result<(), Errno> {
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
SYS_fchmodat,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
mode.bits(),
SYSCOOKIE_POOL.get(CookieIdx::FchmodatArg3),
SYSCOOKIE_POOL.get(CookieIdx::FchmodatArg4),
SYSCOOKIE_POOL.get(CookieIdx::FchmodatArg5),
)
}
})?;
Errno::result(res).map(drop)
}
#[inline(always)]
pub(crate) fn safe_fchmod<Fd: AsFd>(fd: Fd, mode: Mode) -> Result<(), Errno> {
Errno::result(unsafe {
syscall(
SYS_fchmod,
fd.as_fd().as_raw_fd(),
mode.bits(),
SYSCOOKIE_POOL.get(CookieIdx::FchmodArg2),
SYSCOOKIE_POOL.get(CookieIdx::FchmodArg3),
SYSCOOKIE_POOL.get(CookieIdx::FchmodArg4),
SYSCOOKIE_POOL.get(CookieIdx::FchmodArg5),
)
})
.map(drop)
}
#[inline(always)]
pub(crate) fn safe_fchmodat2<Fd: AsFd>(dirfd: Fd, mode: Mode) -> Result<(), Errno> {
let sys_fchmodat2 = SYS_FCHMODAT2.ok_or(Errno::ENOSYS)?;
Errno::result(unsafe {
syscall(
sys_fchmodat2,
dirfd.as_fd().as_raw_fd(),
empty_path() as *const c_char,
mode.bits(),
AtFlags::AT_EMPTY_PATH.bits(),
SYSCOOKIE_POOL.get(CookieIdx::Fchmodat2Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Fchmodat2Arg5),
)
})
.map(drop)
}
#[inline(always)]
pub(crate) fn safe_fchown<Fd: AsFd>(
fd: Fd,
owner: Option<Uid>,
group: Option<Gid>,
) -> Result<(), Errno> {
Errno::result(unsafe {
let (uid, gid) = chown_raw_ids(owner, group);
syscall(
SYS_fchown,
fd.as_fd().as_raw_fd(),
uid,
gid,
SYSCOOKIE_POOL.get(CookieIdx::FchownArg3),
SYSCOOKIE_POOL.get(CookieIdx::FchownArg4),
SYSCOOKIE_POOL.get(CookieIdx::FchownArg5),
)
})
.map(drop)
}
#[inline(always)]
pub(crate) fn safe_fchownat<Fd: AsFd>(
dirfd: Fd,
owner: Option<Uid>,
group: Option<Gid>,
) -> Result<(), Errno> {
Errno::result(unsafe {
let (uid, gid) = chown_raw_ids(owner, group);
syscall(
SYS_fchownat,
dirfd.as_fd().as_raw_fd(),
empty_path() as *const c_char,
uid,
gid,
AtFlags::AT_EMPTY_PATH.bits(),
SYSCOOKIE_POOL.get(CookieIdx::FchownatArg5),
)
})
.map(drop)
}
#[allow(clippy::unnecessary_cast)]
fn chown_raw_ids(owner: Option<Uid>, group: Option<Gid>) -> (uid_t, gid_t) {
let uid = owner
.map(Into::into)
.unwrap_or_else(|| (0 as uid_t).wrapping_sub(1));
let gid = group
.map(Into::into)
.unwrap_or_else(|| (0 as gid_t).wrapping_sub(1));
(uid, gid)
}
#[inline(always)]
pub fn safe_uname() -> Result<UtsName, Errno> {
let mut name = UtsName::default();
Errno::result(unsafe {
syscall(
SYS_uname,
&raw mut name,
SYSCOOKIE_POOL.get(CookieIdx::UnameArg1),
SYSCOOKIE_POOL.get(CookieIdx::UnameArg2),
SYSCOOKIE_POOL.get(CookieIdx::UnameArg3),
SYSCOOKIE_POOL.get(CookieIdx::UnameArg4),
SYSCOOKIE_POOL.get(CookieIdx::UnameArg5),
)
})?;
Ok(name)
}
#[inline(always)]
pub fn safe_umask(mode: Mode) -> Mode {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let prev = unsafe {
syscall(
SYS_umask,
mode.bits(),
SYSCOOKIE_POOL.get(CookieIdx::UmaskArg1),
SYSCOOKIE_POOL.get(CookieIdx::UmaskArg2),
SYSCOOKIE_POOL.get(CookieIdx::UmaskArg3),
SYSCOOKIE_POOL.get(CookieIdx::UmaskArg4),
SYSCOOKIE_POOL.get(CookieIdx::UmaskArg5),
)
} as mode_t;
#[expect(clippy::disallowed_methods)]
Mode::from_bits(prev).expect("[BUG] umask returned invalid Mode")
}
pub static SYS_UTIMENSAT: LazyLock<Option<c_long>> = LazyLock::new(|| resolve_syscall("utimensat"));
pub static SYS_UTIMENSAT_TIME64: LazyLock<Option<c_long>> =
LazyLock::new(|| resolve_syscall("utimensat_time64"));
#[inline(always)]
pub(crate) fn safe_utimensat<Fd: AsFd>(
dirfd: Fd,
atime: &TimeSpec64,
mtime: &TimeSpec64,
) -> Result<(), Errno> {
let fd = dirfd.as_fd().as_raw_fd();
let path = empty_path() as *const c_char;
let flags = AtFlags::AT_EMPTY_PATH.bits() as c_int;
let cookie4 = SYSCOOKIE_POOL.get(CookieIdx::UtimensatArg4);
let cookie5 = SYSCOOKIE_POOL.get(CookieIdx::UtimensatArg5);
if let Some(sys) = *SYS_UTIMENSAT_TIME64 {
let times: [TimeSpec64; 2] = [*atime, *mtime];
Errno::result(unsafe {
syscall(sys, fd, path, &raw const times[0], flags, cookie4, cookie5)
})
.map(drop)
} else if let Some(sys) = *SYS_UTIMENSAT {
#[cfg(target_pointer_width = "32")]
{
use crate::compat::TimeSpec32;
let times32: [TimeSpec32; 2] =
[TimeSpec32::try_from(*atime)?, TimeSpec32::try_from(*mtime)?];
Errno::result(unsafe {
syscall(
sys,
fd,
path,
&raw const times32[0],
flags,
cookie4,
cookie5,
)
})
.map(drop)
}
#[cfg(not(target_pointer_width = "32"))]
{
let times: [TimeSpec64; 2] = [*atime, *mtime];
Errno::result(unsafe {
syscall(sys, fd, path, &raw const times[0], flags, cookie4, cookie5)
})
.map(drop)
}
} else {
Err(Errno::ENOSYS)
}
}
#[inline(always)]
pub fn safe_fgetxattr<Fd: AsFd>(
fd: Fd,
name: &CStr,
mut value: Option<&mut Vec<u8>>,
) -> Result<usize, Errno> {
let (val, len) = match value.as_mut() {
Some(v) => (v.as_mut_ptr() as *mut c_void, v.capacity()),
None => (ptr::null_mut(), 0),
};
let res = unsafe {
syscall(
SYS_fgetxattr,
fd.as_fd().as_raw_fd(),
name.as_ptr(),
val,
len,
SYSCOOKIE_POOL.get(CookieIdx::FgetxattrArg4),
SYSCOOKIE_POOL.get(CookieIdx::FgetxattrArg5),
)
};
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let n = Errno::result(res).map(|r| r as usize)?;
if let Some(value) = value {
unsafe { value.set_len(n) };
}
Ok(n)
}
#[inline(always)]
pub fn safe_flistxattr<Fd: AsFd>(fd: Fd, mut list: Option<&mut Vec<u8>>) -> Result<usize, Errno> {
let (ptr, cap) = match list.as_mut() {
Some(b) => (b.as_mut_ptr().cast::<c_char>(), b.capacity()),
None => (ptr::null_mut(), 0),
};
let res = unsafe {
syscall(
SYS_flistxattr,
fd.as_fd().as_raw_fd(),
ptr,
cap,
SYSCOOKIE_POOL.get(CookieIdx::FlistxattrArg3),
SYSCOOKIE_POOL.get(CookieIdx::FlistxattrArg4),
SYSCOOKIE_POOL.get(CookieIdx::FlistxattrArg5),
)
};
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let n = Errno::result(res).map(|r| r as usize)?;
if let Some(list) = list {
unsafe { list.set_len(n) };
}
Ok(n)
}
#[inline(always)]
pub fn safe_fremovexattr<Fd: AsFd>(fd: Fd, name: &CStr) -> Result<(), Errno> {
Errno::result(unsafe {
syscall(
SYS_fremovexattr,
fd.as_fd().as_raw_fd(),
name.as_ptr(),
SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg2),
SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg3),
SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg4),
SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg5),
)
})
.map(drop)
}
#[inline(always)]
pub fn safe_lremovexattr<P: NixPath + ?Sized>(path: &P, name: &CStr) -> Result<(), Errno> {
let res = path.with_nix_path(|cstr| {
unsafe {
syscall(
SYS_lremovexattr,
cstr.as_ptr(),
name.as_ptr(),
SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg2),
SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg3),
SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg4),
SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg5),
)
}
})?;
Errno::result(res).map(drop)
}
#[inline(always)]
pub fn safe_fsetxattr<Fd: AsFd>(
fd: Fd,
name: &CStr,
value: Option<&[u8]>,
flags: c_int,
) -> Result<(), Errno> {
let (val, len) = if let Some(value) = value.as_ref() {
let val = value.as_ptr() as *const c_void;
let len = value.len();
(val, len)
} else {
(ptr::null(), 0)
};
Errno::result(unsafe {
syscall(
SYS_fsetxattr,
fd.as_fd().as_raw_fd(),
name.as_ptr(),
val,
len,
flags,
SYSCOOKIE_POOL.get(CookieIdx::FsetxattrArg5),
)
})
.map(drop)
}
#[inline(always)]
pub fn safe_lsetxattr<P: NixPath + ?Sized>(
path: &P,
name: &CStr,
value: Option<&[u8]>,
flags: c_int,
) -> Result<(), Errno> {
let (val, len) = if let Some(value) = value.as_ref() {
let val = value.as_ptr() as *const c_void;
let len = value.len();
(val, len)
} else {
(ptr::null(), 0)
};
let res = path.with_nix_path(|c_path| {
unsafe {
syscall(
SYS_lsetxattr,
c_path.as_ptr(),
name.as_ptr(),
val,
len,
flags,
SYSCOOKIE_POOL.get(CookieIdx::LsetxattrArg5),
)
}
})?;
Errno::result(res).map(drop)
}
#[inline(always)]
pub fn safe_pipe2(flags: OFlag) -> Result<(SafeOwnedFd, SafeOwnedFd), Errno> {
let mut fds = MaybeUninit::<[SafeOwnedFd; 2]>::uninit();
Errno::result(unsafe {
syscall(
SYS_pipe2,
fds.as_mut_ptr(),
flags.bits(),
SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg2),
SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg3),
SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg5),
)
})?;
let [read, write] = unsafe { fds.assume_init() };
Ok((read, write))
}
#[inline(always)]
pub fn safe_sendfile<Fd1: AsFd, Fd2: AsFd>(
out_fd: Fd1,
in_fd: Fd2,
count: usize,
) -> Result<usize, Errno> {
if let Some(sys_sendfile64) = *SYS_SENDFILE64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
return Errno::result(unsafe {
syscall(
sys_sendfile64,
out_fd.as_fd().as_raw_fd(),
in_fd.as_fd().as_raw_fd(),
ptr::null_mut::<i64>(),
count,
SYSCOOKIE_POOL.get(CookieIdx::Sendfile64Arg4),
SYSCOOKIE_POOL.get(CookieIdx::Sendfile64Arg5),
)
})
.map(|n| n as usize);
}
let sys_sendfile = SYS_SENDFILE.ok_or(Errno::ENOSYS)?;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
Errno::result(unsafe {
syscall(
sys_sendfile,
out_fd.as_fd().as_raw_fd(),
in_fd.as_fd().as_raw_fd(),
ptr::null_mut::<i64>(),
count,
SYSCOOKIE_POOL.get(CookieIdx::SendfileArg4),
SYSCOOKIE_POOL.get(CookieIdx::SendfileArg5),
)
})
.map(|n| n as usize)
}
#[inline(always)]
pub fn safe_pidfd_open(pid: Pid, mut flags: u32) -> Result<SafeOwnedFd, Errno> {
let pid = if *HAVE_PIDFD_THREAD || flags & PIDFD_THREAD == 0 {
pid
} else {
flags &= !PIDFD_THREAD;
proc_tgid(pid)?
};
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
SYS_pidfd_open,
pid.as_raw(),
flags,
SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg2),
SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg3),
SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg4),
SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg5),
)
})
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
}
#[inline(always)]
pub fn safe_pidfd_getfd<Fd: AsFd>(pid_fd: Fd, remote_fd: RawFd) -> Result<SafeOwnedFd, Errno> {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
syscall(
SYS_pidfd_getfd,
pid_fd.as_fd().as_raw_fd(),
remote_fd,
0,
SYSCOOKIE_POOL.get(CookieIdx::PidfdGetfdArg3),
SYSCOOKIE_POOL.get(CookieIdx::PidfdGetfdArg4),
SYSCOOKIE_POOL.get(CookieIdx::PidfdGetfdArg5),
)
})
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
}
#[inline(always)]
pub fn safe_pidfd_send_signal<Fd: AsFd>(pid_fd: Fd, sig: i32) -> Result<(), Errno> {
Errno::result(unsafe {
syscall(
SYS_pidfd_send_signal,
pid_fd.as_fd().as_raw_fd(),
sig,
0,
0,
SYSCOOKIE_POOL.get(CookieIdx::PidfdSendSignalArg4),
SYSCOOKIE_POOL.get(CookieIdx::PidfdSendSignalArg5),
)
})
.map(drop)
}
#[inline(always)]
pub fn safe_pidfd_is_alive<Fd: AsFd>(pid_fd: Fd) -> Result<(), Errno> {
safe_pidfd_send_signal(pid_fd, 0)
}
#[inline(always)]
pub unsafe fn safe_ptrace(
request: PtraceRequest,
pid: c_int,
addr: *mut c_void,
data: *mut c_void,
) -> c_long {
unsafe {
syscall(
SYS_ptrace,
request,
pid,
addr,
data,
SYSCOOKIE_POOL.get(CookieIdx::PtraceArg4),
SYSCOOKIE_POOL.get(CookieIdx::PtraceArg5),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cookie_idx_1() {
assert_eq!(CookieIdx::Accept4Arg4 as usize, 0);
}
#[test]
fn test_cookie_idx_2() {
assert_eq!(CookieIdx::UtimensatArg5 as usize, CookieIdx::COUNT - 1);
}
#[test]
fn test_cookie_idx_3() {
assert!(CookieIdx::COUNT > 0);
}
#[test]
fn test_cookie_idx_4() {
assert!(CookieIdx::COUNT > 100);
}
#[test]
fn test_cookie_idx_5() {
assert_eq!(
CookieIdx::Accept4Arg5 as usize,
CookieIdx::Accept4Arg4 as usize + 1
);
}
#[test]
fn test_cookie_idx_6() {
assert_eq!(
CookieIdx::BindArg3 as usize,
CookieIdx::Accept4Arg5 as usize + 1
);
}
#[test]
fn test_cookie_idx_7() {
let idx = CookieIdx::CloseArg1;
let cloned = idx;
assert_eq!(idx as usize, cloned as usize);
}
#[test]
fn test_cookie_idx_8() {
let s = format!("{:?}", CookieIdx::Accept4Arg4);
assert_eq!(s, "Accept4Arg4");
}
#[test]
fn test_syscookie_pool_1() {
let a = SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg4);
let b = SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg4);
assert_eq!(a, b);
}
#[test]
fn test_syscookie_pool_2() {
let a = SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg4);
let b = SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg5);
assert_ne!(a, b);
}
#[test]
fn test_syscookie_pool_3() {
let first = SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg4);
let last = SYSCOOKIE_POOL.get(CookieIdx::UnlinkatArg5);
assert_ne!(first, last);
}
#[test]
fn test_syscookie_pool_4() {
let v1 = SYSCOOKIE_POOL.get(CookieIdx::CloseArg1);
let v2 = SYSCOOKIE_POOL.get(CookieIdx::CloseArg1);
let v3 = SYSCOOKIE_POOL.get(CookieIdx::CloseArg1);
assert_eq!(v1, v2);
assert_eq!(v2, v3);
}
#[test]
fn test_syscookie_pool_5() {
let mut all_zero = true;
for i in 0..CookieIdx::COUNT {
let idx: CookieIdx = unsafe { std::mem::transmute(i) };
if SYSCOOKIE_POOL.get(idx) != 0 {
all_zero = false;
break;
}
}
assert!(!all_zero);
}
}