use std::os::fd::AsRawFd;
use libseccomp::ScmpNotifResp;
use memchr::arch::all::{is_prefix, is_suffix};
use nix::{errno::Errno, NixPath};
use crate::{
confine::is_valid_ptr,
kernel::sandbox_path,
lookup::{FileType, FsFlags},
magic::ProcMagic,
path::{XPathBuf, PATH_MAX},
proc::proc_tgid,
req::{SysArg, SysFlags, UNotifyEventRequest},
sandbox::Capability,
};
const READLINK_MAX: usize = PATH_MAX * 16;
pub(crate) fn sys_readlink(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let size = match usize::try_from(req.data.args[2] as i32) {
Ok(0) => return request.fail_syscall(Errno::EINVAL),
Ok(size) => size.min(READLINK_MAX),
Err(_) => return request.fail_syscall(Errno::EINVAL),
};
if !is_valid_ptr(req.data.args[0], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let arg = SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST,
..Default::default()
};
syscall_readlink_handler(request, arg, 1, size)
}
pub(crate) fn sys_readlinkat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let size = match usize::try_from(req.data.args[3] as i32) {
Ok(0) => return request.fail_syscall(Errno::EINVAL),
Ok(size) => size.min(READLINK_MAX),
Err(_) => return request.fail_syscall(Errno::EINVAL),
};
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let arg = SysArg {
dirfd: Some(0),
path: Some(1),
flags: SysFlags::EMPTY_PATH,
fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST,
};
syscall_readlink_handler(request, arg, 2, size)
}
fn syscall_readlink_handler(
request: UNotifyEventRequest,
arg: SysArg,
buf_idx: usize,
buf_siz: usize,
) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let sandbox = request.get_sandbox();
let (path, _, empty_path) = request.read_path(&sandbox, arg)?;
if !empty_path && sandbox.enabled(Capability::CAP_STAT) {
let sysname = if buf_idx == 1 {
"readlink"
} else {
"readlinkat"
};
sandbox_path(
Some(&request),
&sandbox,
request.scmpreq.pid(), path.abs(),
Capability::CAP_STAT,
sysname,
)?;
}
if let Some(file_type) = &path.typ {
if !matches!(file_type, FileType::Lnk | FileType::MagicLnk(_)) {
return if empty_path {
Err(Errno::ENOENT)
} else {
Err(Errno::EINVAL)
};
}
let maybe_magic_self = match file_type {
FileType::MagicLnk(ProcMagic::Pid { pid }) => Some((*pid, None)),
FileType::Lnk if path.abs().is_proc_self(false) => {
Some((request.scmpreq.pid(), None))
}
FileType::MagicLnk(ProcMagic::Tid { tgid, pid }) => Some((*pid, Some(*tgid))),
FileType::Lnk if path.abs().is_proc_self(true) => {
let pid = request.scmpreq.pid();
let tgid = proc_tgid(pid)?;
Some((pid, Some(tgid)))
}
_ => None,
};
if let Some((pid, maybe_tgid)) = maybe_magic_self {
let buf = if let Some(tgid) = maybe_tgid {
XPathBuf::from_task(tgid, pid)
} else {
XPathBuf::from_pid(pid)
}?;
let buf = buf.as_bytes();
let siz = buf.len().min(buf_siz);
request.write_mem_all(&buf[..siz], req.data.args[buf_idx])?;
#[expect(clippy::cast_possible_wrap)]
return Ok(request.return_syscall(siz as i64));
}
}
assert!(path.base().is_empty()); let fd = path.dir();
if !is_valid_ptr(req.data.args[buf_idx], req.data.arch) {
return Err(Errno::EFAULT);
}
let mut buf = Vec::new();
buf.try_reserve(buf_siz).or(Err(Errno::ENOMEM))?;
buf.resize(buf_siz, 0);
#[expect(clippy::cast_sign_loss)]
let size = Errno::result(unsafe {
libc::readlinkat(
fd.as_raw_fd(),
c"".as_ptr(),
buf.as_mut_ptr().cast(),
buf_siz,
)
})
.map(|size| size as usize)?;
let mut buf = &buf[..size];
if is_prefix(buf, b"/memfd:syd") {
buf = &buf[b"/memfd:syd".len()..];
if is_suffix(buf, b" (deleted)") {
buf = &buf[..buf.len().saturating_sub(b" (deleted)".len())];
}
}
let size = buf.len();
request.write_mem_all(buf, req.data.args[buf_idx])?;
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(size as i64))
})
}