#![forbid(unsafe_code)]
use std::os::fd::AsRawFd;
use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, fcntl::AtFlags, sys::stat::Mode, NixPath};
use crate::{
cookie::{safe_fchmod, safe_fchmodat, safe_fchmodat2},
error,
fd::{fd_mode, PROC_FILE},
kernel::{syscall_path_handler, to_atflags, to_mode},
lookup::{CanonicalPath, FileType, FsFlags},
path::XPathBuf,
req::{PathArgs, SysArg, SysFlags, UNotifyEventRequest},
sandbox::SandboxGuard,
};
pub(crate) fn sys_fchmod(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let mode = to_mode(req.data.args[1]);
let argv = &[SysArg {
dirfd: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(request, "fchmod", argv, |path_args, request, sandbox| {
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
assert!(path.base().is_empty());
let umask = sandbox.umask.unwrap_or(Mode::empty());
let restrict_sticky = !sandbox.flags.allow_unsafe_sticky();
let log_scmp = sandbox.log_scmp();
drop(sandbox); let mut mode = mode;
safe_chmod_mode(request, path, &mut mode, umask, restrict_sticky, log_scmp)?;
safe_fchmod(path.dir(), mode).map(|_| request.return_syscall(0))
})
}
pub(crate) fn sys_chmod(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let mode = to_mode(req.data.args[1]);
let argv = &[SysArg {
path: Some(0),
..Default::default()
}];
syscall_path_handler(request, "chmod", argv, |path_args, request, sandbox| {
syscall_chmod_handler(request, sandbox, path_args, mode)
})
}
pub(crate) fn sys_fchmodat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let mode = to_mode(req.data.args[2]);
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
..Default::default()
}];
syscall_path_handler(request, "fchmodat", argv, |path_args, request, sandbox| {
syscall_chmod_handler(request, sandbox, path_args, mode)
})
}
pub(crate) fn sys_fchmodat2(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let atflags = match to_atflags(
req.data.args[3],
AtFlags::AT_EMPTY_PATH | AtFlags::AT_SYMLINK_NOFOLLOW,
) {
Ok(atflags) => atflags,
Err(errno) => return request.fail_syscall(errno),
};
let mode = to_mode(req.data.args[2]);
let mut flags = SysFlags::empty();
let mut fsflags = FsFlags::MUST_PATH;
if atflags.contains(AtFlags::AT_EMPTY_PATH) {
flags |= SysFlags::EMPTY_PATH;
}
if atflags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
fsflags |= FsFlags::NO_FOLLOW_LAST;
}
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
flags,
fsflags,
}];
syscall_path_handler(request, "fchmodat2", argv, |path_args, request, sandbox| {
syscall_chmod_handler(request, sandbox, path_args, mode)
})
}
fn syscall_chmod_handler(
request: &UNotifyEventRequest,
sandbox: SandboxGuard,
args: PathArgs,
mut mode: Mode,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
assert!(path.base().is_empty()); let fd = path.dir();
let umask = sandbox.umask.unwrap_or(Mode::empty());
let restrict_sticky = !sandbox.flags.allow_unsafe_sticky();
let log_scmp = sandbox.log_scmp();
drop(sandbox); safe_chmod_mode(request, path, &mut mode, umask, restrict_sticky, log_scmp)?;
match safe_fchmodat2(fd, mode) {
Ok(_) => Ok(()),
Err(Errno::ENOSYS) => {
let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
safe_fchmodat(PROC_FILE(), &pfd, mode)
}
Err(errno) => Err(errno),
}
.map(|_| request.return_syscall(0))
}
#[expect(clippy::cognitive_complexity)]
fn safe_chmod_mode(
request: &UNotifyEventRequest,
path: &CanonicalPath,
mode: &mut Mode,
umask: Mode,
restrict_sticky: bool,
log_scmp: bool,
) -> Result<(), Errno> {
match path.typ {
Some(FileType::Reg) => *mode &= !umask,
Some(FileType::Dir)
if restrict_sticky
&& !mode.contains(Mode::S_ISVTX)
&& fd_mode(path.dir())?.contains(Mode::S_ISVTX) =>
{
mode.insert(Mode::S_ISVTX);
if log_scmp {
error!("ctx": "immutable_sticky",
"path": path.abs(), "mode": mode.bits(),
"msg": "blocked attempt to unset sticky bit",
"tip": "fix your program or use `trace/allow_unsafe_sticky:1'",
"req": request);
} else {
error!("ctx": "immutable_sticky",
"path": path.abs(), "mode": mode.bits(),
"msg": "blocked attempt to unset sticky bit",
"tip": "fix your program or use `trace/allow_unsafe_sticky:1'",
"pid": request.scmpreq.pid);
}
}
_ => {}
}
Ok(())
}