#![forbid(unsafe_code)]
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
sys::stat::{Mode, SFlag},
};
use crate::{
cookie::{safe_mknodat, safe_umask},
kernel::{syscall_path_handler, to_mode},
lookup::FsFlags,
proc::proc_umask,
req::{PathArgs, SysArg, UNotifyEventRequest},
};
pub(crate) fn sys_mknod(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let kind = match to_sflag(req.data.args[1]) {
Ok(kind) => kind,
Err(errno) => return request.fail_syscall(errno),
};
let perm = to_mode(req.data.args[1]);
#[expect(clippy::useless_conversion)]
let dev: libc::dev_t = match req.data.args[2].try_into() {
Ok(dev) => dev,
Err(_) => return request.fail_syscall(Errno::EINVAL),
};
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MISS_LAST | FsFlags::NO_FOLLOW_LAST | FsFlags::DOTLAST_EEXIST,
..Default::default()
}];
syscall_path_handler(request, "mknod", argv, |path_args, request, sandbox| {
let umask = sandbox.umask;
drop(sandbox); syscall_mknod_handler(request, path_args, kind, perm, dev, umask)
})
}
pub(crate) fn sys_mknodat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let kind = match to_sflag(req.data.args[2]) {
Ok(kind) => kind,
Err(errno) => return request.fail_syscall(errno),
};
let perm = to_mode(req.data.args[2]);
#[expect(clippy::useless_conversion)]
let dev: libc::dev_t = match req.data.args[3].try_into() {
Ok(dev) => dev,
Err(_) => return request.fail_syscall(Errno::EINVAL),
};
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
fsflags: FsFlags::MISS_LAST | FsFlags::NO_FOLLOW_LAST | FsFlags::DOTLAST_EEXIST,
..Default::default()
}];
syscall_path_handler(request, "mknodat", argv, |path_args, request, sandbox| {
let umask = sandbox.umask;
drop(sandbox); syscall_mknod_handler(request, path_args, kind, perm, dev, umask)
})
}
fn syscall_mknod_handler(
request: &UNotifyEventRequest,
args: PathArgs,
kind: SFlag,
mut perm: Mode,
dev: libc::dev_t,
force_umask: Option<Mode>,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
if kind == SFlag::S_IFREG {
if let Some(mask) = force_umask {
perm &= !mask;
}
}
let req = request.scmpreq;
let mask = proc_umask(req.pid())?;
safe_umask(mask);
request.cache.add_sys_block(req, false)?;
let result = safe_mknodat(path.dir(), path.base(), kind, perm, dev);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}
fn to_sflag(arg: u64) -> Result<SFlag, Errno> {
#[expect(clippy::cast_possible_truncation)]
let kind = (arg as libc::mode_t) & SFlag::S_IFMT.bits();
let kind = if kind == 0 {
SFlag::S_IFREG
} else {
SFlag::from_bits(kind).ok_or(Errno::EINVAL)?
};
match kind {
SFlag::S_IFREG | SFlag::S_IFCHR | SFlag::S_IFBLK | SFlag::S_IFIFO | SFlag::S_IFSOCK => {
Ok(kind)
}
SFlag::S_IFDIR => Err(Errno::EPERM),
_ => Err(Errno::EINVAL),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_sflag_zero_is_reg_1() {
assert_eq!(to_sflag(0), Ok(SFlag::S_IFREG));
}
#[test]
fn test_to_sflag_reg_1() {
assert_eq!(to_sflag(SFlag::S_IFREG.bits() as u64), Ok(SFlag::S_IFREG));
}
#[test]
fn test_to_sflag_chr_1() {
assert_eq!(to_sflag(SFlag::S_IFCHR.bits() as u64), Ok(SFlag::S_IFCHR));
}
#[test]
fn test_to_sflag_blk_1() {
assert_eq!(to_sflag(SFlag::S_IFBLK.bits() as u64), Ok(SFlag::S_IFBLK));
}
#[test]
fn test_to_sflag_fifo_1() {
assert_eq!(to_sflag(SFlag::S_IFIFO.bits() as u64), Ok(SFlag::S_IFIFO));
}
#[test]
fn test_to_sflag_sock_1() {
assert_eq!(to_sflag(SFlag::S_IFSOCK.bits() as u64), Ok(SFlag::S_IFSOCK));
}
#[test]
fn test_to_sflag_dir_is_eperm_1() {
assert_eq!(to_sflag(SFlag::S_IFDIR.bits() as u64), Err(Errno::EPERM));
}
#[test]
fn test_to_sflag_high_bits_truncated_1() {
let high = SFlag::S_IFREG.bits() as u64 | (1u64 << 32);
assert_eq!(to_sflag(high), Ok(SFlag::S_IFREG));
}
}