use std::{mem::size_of, os::fd::RawFd};
use libc::c_int;
use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, fcntl::OFlag};
use crate::{
confine::scmp_arch_has_legacy_pipe,
cookie::{safe_exit_group, safe_pipe2},
fd::{get_pipe_size, set_pipe_size},
log_enabled,
path::XPath,
req::UNotifyEventRequest,
sandbox::{Action, Capability, SandboxGuard},
syslog::LogLevel,
warn, xfmt,
};
pub(crate) fn sys_pipe(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let legacy = scmp_arch_has_legacy_pipe(req.data.arch);
handle_pipe(&request, req.data.args[0], OFlag::empty(), legacy)
})
}
pub(crate) fn sys_pipe2(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let flags = to_pipe2_flags(req.data.args[1])?;
handle_pipe(&request, req.data.args[0], flags, false )
})
}
fn handle_pipe(
request: &UNotifyEventRequest,
fdptr: u64,
mut flags: OFlag,
legacy: bool,
) -> Result<ScmpNotifResp, Errno> {
let notification = flags.contains(O_NOTIFICATION_PIPE);
let sandbox = request.get_sandbox();
let force_cloexec = sandbox.flags.force_cloexec();
let force_rand_fd = sandbox.flags.force_rand_fd();
let pipe_max = sandbox.pipe_max;
sandbox_pipe(request, &sandbox, notification)?;
if legacy {
return Ok(unsafe { request.continue_syscall() });
}
drop(sandbox);
let cloexec = force_cloexec || flags.contains(OFlag::O_CLOEXEC);
flags.insert(OFlag::O_CLOEXEC);
let (fd0, fd1) = safe_pipe2(flags)?;
if !notification && get_pipe_size(&fd0)? > pipe_max {
set_pipe_size(&fd0, pipe_max)?;
}
if request.get_fault(fdptr, size_of::<[RawFd; 2]>())? > 0 {
return Err(Errno::EFAULT);
}
let newfd0 = request.add_fd(fd0, cloexec, force_rand_fd)?;
let newfd1 = request.add_fd(fd1, cloexec, force_rand_fd)?;
let a = newfd0.to_ne_bytes();
let b = newfd1.to_ne_bytes();
let out = [a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]];
request.write_mem_all(&out, fdptr)?;
Ok(request.return_syscall(0))
}
#[expect(clippy::cognitive_complexity)]
fn sandbox_pipe(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard<'_>,
notification: bool,
) -> Result<(), Errno> {
let caps = Capability::CAP_CREATE;
let name = if notification {
XPath::from_bytes(b"!notification_pipe")
} else {
XPath::from_bytes(b"!pipe")
};
let deny_notif = notification && !sandbox.options.allow_unsafe_pipe();
if sandbox.getcaps(caps).is_empty() {
return if deny_notif {
Err(Errno::ENOPKG)
} else {
Ok(())
};
}
let action = sandbox.check_name(caps, name);
if action.is_logging() && log_enabled!(LogLevel::Warn) {
if sandbox.log_scmp() {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": request.syscall, "path": &name,
"tip": xfmt!("configure `allow/{caps}+{name}'"),
"req": request);
} else {
warn!("ctx": "access", "cap": caps, "act": action,
"sys": request.syscall, "path": &name,
"tip": xfmt!("configure `allow/{caps}+{name}'"),
"pid": request.scmpreq.pid);
}
}
let deny_errno = if notification {
Errno::ENOPKG
} else {
Errno::ENOMEM
};
match action {
Action::Allow | Action::Warn if deny_notif => Err(Errno::ENOPKG),
Action::Allow | Action::Warn => Ok(()),
Action::Deny | Action::Filter => Err(deny_errno),
Action::Panic => panic!(),
Action::Exit => safe_exit_group(deny_errno as i32),
action => {
let _ = request.kill(action);
Err(deny_errno)
}
}
}
const O_NOTIFICATION_PIPE: OFlag = OFlag::O_EXCL;
const PIPE2_VALID: OFlag = OFlag::O_CLOEXEC
.union(OFlag::O_NONBLOCK)
.union(OFlag::O_DIRECT)
.union(O_NOTIFICATION_PIPE);
fn to_pipe2_flags(arg: u64) -> Result<OFlag, Errno> {
#[expect(clippy::cast_possible_truncation)]
let flags = arg as c_int;
let flags = OFlag::from_bits_retain(flags);
if !flags.difference(PIPE2_VALID).is_empty() {
return Err(Errno::EINVAL);
}
Ok(flags)
}