#![forbid(unsafe_code)]
use std::os::fd::AsFd;
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
fcntl::{fcntl, FcntlArg, OFlag},
};
use crate::{
confine::scmp_arch_is_compat_long32,
fd::to_fd,
lookup::{CanonicalPath, FileInfo},
req::UNotifyEventRequest,
sandbox::Capability,
};
const F_SETFL: u64 = libc::F_SETFL as u64;
const F_OFD_SETLK: u64 = libc::F_OFD_SETLK as u64;
const F_OFD_SETLKW: u64 = libc::F_OFD_SETLKW as u64;
const O_APPEND: u64 = libc::O_APPEND as u64;
pub(crate) fn sys_fcntl(request: UNotifyEventRequest) -> ScmpNotifResp {
let is32 = scmp_arch_is_compat_long32(request.scmpreq.data.arch);
handle_fcntl(request, is32)
}
pub(crate) fn sys_fcntl64(request: UNotifyEventRequest) -> ScmpNotifResp {
handle_fcntl(request, false)
}
fn handle_fcntl(request: UNotifyEventRequest, _is32: bool) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let args = req.data.args;
#[expect(clippy::cast_possible_truncation)]
let cmd = u64::from(args[1] as u32);
let arg = args[2];
assert!(
matches!(cmd, F_SETFL | F_OFD_SETLK | F_OFD_SETLKW),
"BUG: called fcntl(2) handler with invalid command {cmd:#x}, report a bug!"
);
assert!(
cmd != F_SETFL || arg & O_APPEND == 0,
"BUG: called fcntl(2) handler with F_SETFL command and O_APPEND set, report a bug!"
);
let fd = to_fd(args[0])?;
let fd = request.get_fd(fd)?;
let path = CanonicalPath::new_fd(fd.into(), req.pid()).or(Err(Errno::EBADF))?;
if !request.is_valid() {
return Err(Errno::ESRCH);
}
let sandbox = request.get_sandbox();
let is_append = sandbox.is_append(path.abs());
let is_crypt = sandbox.enabled(Capability::CAP_CRYPT);
drop(sandbox);
if is_append && cmd == F_SETFL {
return Err(Errno::EPERM);
}
let fd = path.dir();
if is_crypt {
if let Ok(info) = FileInfo::from_fd(fd) {
#[expect(clippy::disallowed_methods)]
let files = request.cache.crypt_map.as_ref().unwrap();
let deny = {
let files = files.0.lock().unwrap_or_else(|err| err.into_inner());
files.values().any(|map| map.info == info)
};
if deny {
return Err(Errno::EPERM);
}
}
}
match cmd {
F_SETFL => handle_fcntl_setfl(fd, arg),
F_OFD_SETLK => handle_fcntl_ofd_setlk(&request, fd, arg, false),
F_OFD_SETLKW => handle_fcntl_ofd_setlkw(&request, fd, arg, false),
_ => unreachable!(
"BUG: called fcntl(2) handler with invalid command {cmd:#x}, report a bug!"
),
}
.map(|ret| request.return_syscall(ret.into()))
})
}
fn handle_fcntl_setfl<Fd: AsFd>(fd: Fd, arg: u64) -> Result<i32, Errno> {
#[expect(clippy::cast_possible_truncation)]
let flags = OFlag::from_bits_retain(arg as i32);
fcntl(fd, FcntlArg::F_SETFL(flags))
}
fn handle_fcntl_ofd_setlk<Fd: AsFd>(
request: &UNotifyEventRequest,
fd: Fd,
addr: u64,
is32: bool,
) -> Result<i32, Errno> {
let flock = request.remote_flock(addr, is32)?;
fcntl(fd, FcntlArg::F_OFD_SETLK(&flock))
}
fn handle_fcntl_ofd_setlkw<Fd: AsFd>(
request: &UNotifyEventRequest,
fd: Fd,
addr: u64,
is32: bool,
) -> Result<i32, Errno> {
let flock = request.remote_flock(addr, is32)?;
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock))
}