#![forbid(unsafe_code)]
use libc::{c_int, clockid_t};
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
sys::timerfd::{ClockId, TimerFlags},
};
use crate::{
caps::{util::capable, Capabilities},
cookie::{safe_exit_group, safe_timerfd_create},
log_enabled,
path::XPath,
req::UNotifyEventRequest,
sandbox::{Action, Capability, SandboxGuard},
syslog::LogLevel,
warn, xfmt,
};
pub(crate) fn sys_timerfd_create(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let flags = to_timer_flags(req.data.args[1])?;
let clockid = to_clock_id(req.data.args[0])?;
if matches!(
clockid,
ClockId::CLOCK_BOOTTIME_ALARM | ClockId::CLOCK_REALTIME_ALARM
) && !capable(Capabilities::CAP_WAKE_ALARM)
{
return Err(Errno::EPERM);
}
handle_timerfd_create(&request, clockid, flags)
})
}
fn handle_timerfd_create(
request: &UNotifyEventRequest,
clockid: ClockId,
mut flags: TimerFlags,
) -> Result<ScmpNotifResp, Errno> {
let sandbox = request.get_sandbox();
let force_cloexec = sandbox.flags.force_cloexec();
let force_rand_fd = sandbox.flags.force_rand_fd();
sandbox_timerfd(request, &sandbox)?;
drop(sandbox);
let cloexec = force_cloexec || flags.contains(TimerFlags::TFD_CLOEXEC);
flags.insert(TimerFlags::TFD_CLOEXEC);
let fd = safe_timerfd_create(clockid, flags)?;
request.send_fd(fd, cloexec, force_rand_fd)
}
#[expect(clippy::cognitive_complexity)]
fn sandbox_timerfd(request: &UNotifyEventRequest, sandbox: &SandboxGuard<'_>) -> Result<(), Errno> {
let caps = Capability::CAP_CREATE;
if sandbox.getcaps(caps).is_empty() {
return Ok(());
}
let name = XPath::from_bytes(b"!timerfd");
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);
}
}
match action {
Action::Allow | Action::Warn => Ok(()),
Action::Deny | Action::Filter => Err(Errno::ENODEV),
Action::Panic => panic!(),
Action::Exit => safe_exit_group(Errno::ENODEV as i32),
action => {
let _ = request.kill(action);
Err(Errno::ENODEV)
}
}
}
fn to_clock_id(arg: u64) -> Result<ClockId, Errno> {
#[expect(clippy::cast_possible_truncation)]
let clockid = arg as clockid_t;
if clockid == ClockId::CLOCK_MONOTONIC as clockid_t {
Ok(ClockId::CLOCK_MONOTONIC)
} else if clockid == ClockId::CLOCK_REALTIME as clockid_t {
Ok(ClockId::CLOCK_REALTIME)
} else if clockid == ClockId::CLOCK_REALTIME_ALARM as clockid_t {
Ok(ClockId::CLOCK_REALTIME_ALARM)
} else if clockid == ClockId::CLOCK_BOOTTIME as clockid_t {
Ok(ClockId::CLOCK_BOOTTIME)
} else if clockid == ClockId::CLOCK_BOOTTIME_ALARM as clockid_t {
Ok(ClockId::CLOCK_BOOTTIME_ALARM)
} else {
Err(Errno::EINVAL)
}
}
fn to_timer_flags(arg: u64) -> Result<TimerFlags, Errno> {
#[expect(clippy::cast_possible_truncation)]
let flags = arg as c_int;
TimerFlags::from_bits(flags).ok_or(Errno::EINVAL)
}