#![forbid(unsafe_code)]
use libseccomp::ScmpNotifResp;
use nix::{
fcntl::AtFlags,
unistd::{Gid, Uid},
NixPath,
};
use crate::{
confine::scmp_arch_has_uid16,
cookie::{safe_fchown, safe_fchownat},
kernel::{syscall_path_handler, to_atflags, to_id16},
lookup::FsFlags,
req::{SysArg, SysFlags, UNotifyEventRequest},
};
pub(crate) fn sys_fchown(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_fchown_handler(request, "fchown", true)
}
pub(crate) fn sys_fchown32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_fchown_handler(request, "fchown32", false)
}
pub(crate) fn sys_chown(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_chown_handler(request, "chown", true)
}
pub(crate) fn sys_chown32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_chown_handler(request, "chown32", false)
}
pub(crate) fn sys_lchown(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_lchown_handler(request, "lchown", true)
}
pub(crate) fn sys_lchown32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_lchown_handler(request, "lchown32", false)
}
pub(crate) fn sys_fchownat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let flags = match to_atflags(
req.data.args[4],
AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH,
) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
let mut fsflags = FsFlags::MUST_PATH;
if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
fsflags |= FsFlags::NO_FOLLOW_LAST;
}
let empty = flags.contains(AtFlags::AT_EMPTY_PATH);
let mut flags = SysFlags::empty();
if empty {
flags |= SysFlags::EMPTY_PATH;
}
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
flags,
fsflags,
}];
syscall_path_handler(request, "fchownat", argv, |path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
assert!(path.base().is_empty());
#[expect(clippy::cast_possible_truncation)]
let owner = match req.data.args[2] as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let group = match req.data.args[3] as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
request.cache.add_sys_block(req, false)?;
let result = safe_fchownat(path.dir(), owner, group);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
})
}
fn syscall_fchown_handler(
request: UNotifyEventRequest,
name: &'static str,
is_16: bool,
) -> ScmpNotifResp {
let argv = &[SysArg {
dirfd: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(request, name, argv, |path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
assert!(path.base().is_empty());
let req = request.scmpreq;
let (arg1, arg2) = if is_16 && scmp_arch_has_uid16(req.data.arch) {
(to_id16(req.data.args[1]), to_id16(req.data.args[2]))
} else {
(req.data.args[1], req.data.args[2])
};
#[expect(clippy::cast_possible_truncation)]
let owner = match arg1 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let group = match arg2 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
request.cache.add_sys_block(req, false)?;
let result = safe_fchown(path.dir(), owner, group);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
})
}
fn syscall_chown_handler(
request: UNotifyEventRequest,
name: &'static str,
is_16: bool,
) -> ScmpNotifResp {
let argv = &[SysArg {
path: Some(0),
..Default::default()
}];
syscall_path_handler(request, name, argv, |path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
assert!(path.base().is_empty());
let req = request.scmpreq;
let (arg1, arg2) = if is_16 && scmp_arch_has_uid16(req.data.arch) {
(to_id16(req.data.args[1]), to_id16(req.data.args[2]))
} else {
(req.data.args[1], req.data.args[2])
};
#[expect(clippy::cast_possible_truncation)]
let owner = match arg1 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let group = match arg2 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
request.cache.add_sys_block(req, false)?;
let result = safe_fchownat(path.dir(), owner, group);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
})
}
fn syscall_lchown_handler(
request: UNotifyEventRequest,
name: &'static str,
is_16: bool,
) -> ScmpNotifResp {
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST,
..Default::default()
}];
syscall_path_handler(request, name, argv, |path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
assert!(path.base().is_empty());
let req = request.scmpreq;
let (arg1, arg2) = if is_16 && scmp_arch_has_uid16(req.data.arch) {
(to_id16(req.data.args[1]), to_id16(req.data.args[2]))
} else {
(req.data.args[1], req.data.args[2])
};
#[expect(clippy::cast_possible_truncation)]
let owner = match arg1 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let group = match arg2 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
request.cache.add_sys_block(req, false)?;
let result = safe_fchownat(path.dir(), owner, group);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
})
}