#![forbid(unsafe_code)]
use std::os::fd::AsRawFd;
use libc::c_int;
use libseccomp::{ScmpArch, ScmpNotifResp};
use nix::errno::Errno;
use crate::{
compat::FallocateFlags,
confine::{scmp_arch_bits, scmp_arch_is_big_endian},
cookie::{safe_fallocate, safe_ftruncate, safe_ftruncate64, safe_truncate, safe_truncate64},
fd::is_valid_fd,
kernel::syscall_path_handler,
lookup::FileType,
path::XPathBuf,
req::{SysArg, UNotifyEventRequest},
};
pub(crate) fn sys_truncate(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let is32 = scmp_arch_bits(req.data.arch) == 32;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let len = if is32 {
libc::off_t::from(req.data.args[1] as i32)
} else {
req.data.args[1] as libc::off_t
};
if len < 0 {
return request.fail_syscall(Errno::EINVAL);
} else if req.data.args[0] == 0 {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
path: Some(0),
..Default::default()
}];
syscall_path_handler(request, "truncate", argv, |path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
if matches!(path.typ, Some(FileType::Dir)) {
return Err(Errno::EISDIR);
}
let fd = path.dir.as_ref().ok_or(Errno::EINVAL)?;
let mut pfd = XPathBuf::from("/proc/thread-self/fd");
pfd.push_fd(fd.as_raw_fd());
request.cache.add_sys_block(req, false)?;
let result = safe_truncate(&pfd, len).map(|_| request.return_syscall(0));
request.cache.del_sys_block(req.id)?;
result
})
}
pub(crate) fn sys_truncate64(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let is32 = scmp_arch_bits(req.data.arch) == 32;
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let len = if is32 {
let arg_no = match req.data.arch {
ScmpArch::Arm | ScmpArch::Mips | ScmpArch::Mipsel | ScmpArch::Ppc => 2,
_ => 1,
};
let len_low = req.data.args[arg_no] as u32;
let len_high = req.data.args[arg_no + 1] as u32;
if scmp_arch_is_big_endian(req.data.arch) {
(libc::off64_t::from(len_low) << 32) | libc::off64_t::from(len_high)
} else {
(libc::off64_t::from(len_high) << 32) | libc::off64_t::from(len_low)
}
} else {
let arg_no = match req.data.arch {
ScmpArch::Aarch64 | ScmpArch::Ppc64 | ScmpArch::Ppc64Le => 2,
_ => 1,
};
req.data.args[arg_no] as libc::off64_t
};
if len < 0 {
return request.fail_syscall(Errno::EINVAL);
} else if req.data.args[0] == 0 {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
path: Some(0),
..Default::default()
}];
syscall_path_handler(
request,
"truncate64",
argv,
|path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
if matches!(path.typ, Some(FileType::Dir)) {
return Err(Errno::EISDIR);
}
let fd = path.dir.as_ref().ok_or(Errno::EINVAL)?;
let mut pfd = XPathBuf::from("/proc/thread-self/fd");
pfd.push_fd(fd.as_raw_fd());
request.cache.add_sys_block(req, false)?;
let result = safe_truncate64(&pfd, len).map(|_| request.return_syscall(0));
request.cache.del_sys_block(req.id)?;
result
},
)
}
pub(crate) fn sys_ftruncate(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let is32 = scmp_arch_bits(req.data.arch) == 32;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let len = if is32 {
libc::off_t::from(req.data.args[1] as i32)
} else {
req.data.args[1] as libc::off_t
};
if len < 0 {
return request.fail_syscall(Errno::EINVAL);
}
if !is_valid_fd(req.data.args[0]) {
return request.fail_syscall(Errno::EBADF);
}
let argv = &[SysArg {
dirfd: Some(0),
..Default::default()
}];
syscall_path_handler(request, "ftruncate", argv, |path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let fd = path_args
.0
.as_ref()
.unwrap()
.path
.dir
.as_ref()
.ok_or(Errno::EINVAL)?;
request.cache.add_sys_block(req, false)?;
let result = safe_ftruncate(fd, len).map(|_| request.return_syscall(0));
request.cache.del_sys_block(req.id)?;
result
})
}
pub(crate) fn sys_ftruncate64(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let is32 = scmp_arch_bits(req.data.arch) == 32;
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let len = if is32 {
let arg_no = match req.data.arch {
ScmpArch::Arm | ScmpArch::Mips | ScmpArch::Mipsel | ScmpArch::Ppc => 2,
_ => 1,
};
let len_low = req.data.args[arg_no] as u32;
let len_high = req.data.args[arg_no + 1] as u32;
if scmp_arch_is_big_endian(req.data.arch) {
(libc::off64_t::from(len_low) << 32) | libc::off64_t::from(len_high)
} else {
(libc::off64_t::from(len_high) << 32) | libc::off64_t::from(len_low)
}
} else {
req.data.args[1] as libc::off64_t
};
if len < 0 {
return request.fail_syscall(Errno::EINVAL);
}
if !is_valid_fd(req.data.args[0]) {
return request.fail_syscall(Errno::EBADF);
}
let argv = &[SysArg {
dirfd: Some(0),
..Default::default()
}];
syscall_path_handler(
request,
"ftruncate64",
argv,
|path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let fd = path_args
.0
.as_ref()
.unwrap()
.path
.dir
.as_ref()
.ok_or(Errno::EINVAL)?;
request.cache.add_sys_block(req, false)?;
let result = safe_ftruncate64(fd, len).map(|_| request.return_syscall(0));
request.cache.del_sys_block(req.id)?;
result
},
)
}
pub(crate) fn sys_fallocate(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
if !is_valid_fd(req.data.args[0]) {
return request.fail_syscall(Errno::EBADF);
}
let is32 = scmp_arch_bits(req.data.arch) == 32;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let off = if is32 {
let len_low = req.data.args[2] as u32;
let len_high = req.data.args[3] as u32;
if scmp_arch_is_big_endian(req.data.arch) {
(libc::off64_t::from(len_low) << 32) | libc::off64_t::from(len_high)
} else {
(libc::off64_t::from(len_high) << 32) | libc::off64_t::from(len_low)
}
} else {
req.data.args[2] as libc::off64_t
};
if off < 0 {
return request.fail_syscall(Errno::EINVAL);
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let len = if is32 {
let len_low = req.data.args[4] as u32;
let len_high = req.data.args[5] as u32;
if scmp_arch_is_big_endian(req.data.arch) {
(libc::off64_t::from(len_low) << 32) | libc::off64_t::from(len_high)
} else {
(libc::off64_t::from(len_high) << 32) | libc::off64_t::from(len_low)
}
} else {
req.data.args[3] as libc::off64_t
};
if len <= 0 {
return request.fail_syscall(Errno::EINVAL);
}
let mode = match to_fallocate_flags(req.data.args[1]) {
Ok(mode) => mode,
Err(errno) => return request.fail_syscall(errno),
};
let argv = &[SysArg {
dirfd: Some(0),
..Default::default()
}];
syscall_path_handler(request, "fallocate", argv, |path_args, request, sandbox| {
drop(sandbox);
#[expect(clippy::disallowed_methods)]
let fd = path_args
.0
.as_ref()
.unwrap()
.path
.dir
.as_ref()
.ok_or(Errno::EINVAL)?;
request.cache.add_sys_block(req, false)?;
let result = safe_fallocate(fd, mode, off, len);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
})
}
fn to_fallocate_flags(arg: u64) -> Result<FallocateFlags, Errno> {
const FALLOC_FL_MODE_MASK: c_int = FallocateFlags::FALLOC_FL_PUNCH_HOLE.bits()
| FallocateFlags::FALLOC_FL_COLLAPSE_RANGE.bits()
| FallocateFlags::FALLOC_FL_ZERO_RANGE.bits()
| FallocateFlags::FALLOC_FL_INSERT_RANGE.bits()
| FallocateFlags::FALLOC_FL_UNSHARE_RANGE.bits()
| FallocateFlags::FALLOC_FL_WRITE_ZEROES.bits();
#[expect(clippy::cast_possible_truncation)]
let arg: c_int = arg as c_int;
if (arg & !FallocateFlags::all().bits()) != 0 {
return Err(Errno::EOPNOTSUPP);
}
let flags = FallocateFlags::from_bits_truncate(arg);
if (arg & !(FALLOC_FL_MODE_MASK | FallocateFlags::FALLOC_FL_KEEP_SIZE.bits())) != 0 {
return Err(Errno::EOPNOTSUPP);
}
match arg & FALLOC_FL_MODE_MASK {
0 => { }
x if x == FallocateFlags::FALLOC_FL_UNSHARE_RANGE.bits() => {}
x if x == FallocateFlags::FALLOC_FL_ZERO_RANGE.bits() => {}
x if x == FallocateFlags::FALLOC_FL_PUNCH_HOLE.bits() => {
if (arg & FallocateFlags::FALLOC_FL_KEEP_SIZE.bits()) == 0 {
return Err(Errno::EOPNOTSUPP);
}
}
x if x == FallocateFlags::FALLOC_FL_COLLAPSE_RANGE.bits()
|| x == FallocateFlags::FALLOC_FL_INSERT_RANGE.bits()
|| x == FallocateFlags::FALLOC_FL_WRITE_ZEROES.bits() =>
{
if (arg & FallocateFlags::FALLOC_FL_KEEP_SIZE.bits()) != 0 {
return Err(Errno::EOPNOTSUPP);
}
}
_ => return Err(Errno::EOPNOTSUPP),
}
Ok(flags)
}
#[cfg(test)]
mod tests {
use nix::errno::Errno;
use super::*;
use crate::compat::FallocateFlags;
#[test]
fn test_to_fallocate_flags_zero_1() {
let result = to_fallocate_flags(0);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_to_fallocate_flags_keep_size_1() {
let result = to_fallocate_flags(FallocateFlags::FALLOC_FL_KEEP_SIZE.bits() as u64);
assert!(result.is_ok());
assert!(result
.unwrap()
.contains(FallocateFlags::FALLOC_FL_KEEP_SIZE));
}
#[test]
fn test_to_fallocate_flags_punch_hole_needs_keep_size_1() {
let flags = FallocateFlags::FALLOC_FL_PUNCH_HOLE.bits() as u64;
let result = to_fallocate_flags(flags);
assert_eq!(result, Err(Errno::EOPNOTSUPP));
}
#[test]
fn test_to_fallocate_flags_punch_hole_with_keep_size_1() {
let flags = (FallocateFlags::FALLOC_FL_PUNCH_HOLE | FallocateFlags::FALLOC_FL_KEEP_SIZE)
.bits() as u64;
let result = to_fallocate_flags(flags);
assert!(result.is_ok());
}
#[test]
fn test_to_fallocate_flags_zero_range_1() {
let flags = FallocateFlags::FALLOC_FL_ZERO_RANGE.bits() as u64;
let result = to_fallocate_flags(flags);
assert!(result.is_ok());
}
#[test]
fn test_to_fallocate_flags_collapse_range_no_keep_size_1() {
let flags = FallocateFlags::FALLOC_FL_COLLAPSE_RANGE.bits() as u64;
let result = to_fallocate_flags(flags);
assert!(result.is_ok());
}
#[test]
fn test_to_fallocate_flags_collapse_range_with_keep_size_1() {
let flags = (FallocateFlags::FALLOC_FL_COLLAPSE_RANGE | FallocateFlags::FALLOC_FL_KEEP_SIZE)
.bits() as u64;
let result = to_fallocate_flags(flags);
assert_eq!(result, Err(Errno::EOPNOTSUPP));
}
#[test]
fn test_to_fallocate_flags_insert_range_no_keep_size_1() {
let flags = FallocateFlags::FALLOC_FL_INSERT_RANGE.bits() as u64;
let result = to_fallocate_flags(flags);
assert!(result.is_ok());
}
#[test]
fn test_to_fallocate_flags_unshare_range_1() {
let flags = FallocateFlags::FALLOC_FL_UNSHARE_RANGE.bits() as u64;
let result = to_fallocate_flags(flags);
assert!(result.is_ok());
}
#[test]
fn test_to_fallocate_flags_write_zeroes_1() {
let flags = FallocateFlags::FALLOC_FL_WRITE_ZEROES.bits() as u64;
let result = to_fallocate_flags(flags);
assert!(result.is_ok());
}
#[test]
fn test_to_fallocate_flags_invalid_bits_1() {
let flags: u64 = 0x1000;
let result = to_fallocate_flags(flags);
assert_eq!(result, Err(Errno::EOPNOTSUPP));
}
#[test]
fn test_to_fallocate_flags_multiple_modes_1() {
let flags = (FallocateFlags::FALLOC_FL_PUNCH_HOLE | FallocateFlags::FALLOC_FL_ZERO_RANGE)
.bits() as u64;
let result = to_fallocate_flags(flags);
assert_eq!(result, Err(Errno::EOPNOTSUPP));
}
}