#![forbid(unsafe_code)]
use std::{
borrow::Cow,
ffi::CStr,
os::fd::{AsFd, AsRawFd},
};
use libc::{c_int, XATTR_CREATE, XATTR_REPLACE};
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
fcntl::{AtFlags, OFlag},
};
use crate::{
compat::{ResolveFlag, XATTR_LIST_MAX, XATTR_SIZE_MAX},
confine::is_valid_ptr,
cookie::{safe_fgetxattr, safe_flistxattr, safe_fremovexattr, safe_fsetxattr},
fd::{fd_status_flags, to_fd, to_valid_fd, PROC_FILE},
kernel::{syscall_path_handler, to_atflags},
lookup::{safe_open_msym, FsFlags},
path::XPathBuf,
req::{PathArgs, SysArg, SysFlags, UNotifyEventRequest},
sandbox::SandboxGuard,
xattr::{denyxattr, filterxattr},
};
pub(crate) fn sys_getxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
if len != 0 && req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(request, "getxattr", argv, |path_args, request, sandbox| {
syscall_getxattr_handler(request, &sandbox, path_args, &name, len, true)
})
}
pub(crate) fn sys_lgetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
if len != 0 && req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST,
..Default::default()
}];
syscall_path_handler(request, "lgetxattr", argv, |path_args, request, sandbox| {
syscall_lgetxattr_handler(request, &sandbox, path_args, &name, len)
})
}
pub(crate) fn sys_fgetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
if len != 0 && req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let argv = &[SysArg {
dirfd: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(request, "fgetxattr", argv, |path_args, request, sandbox| {
syscall_getxattr_handler(request, &sandbox, path_args, &name, len, false)
})
}
pub(crate) fn sys_getxattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let xargs = match request.remote_xattr_args(req.data.args[4], req.data.args[5]) {
Ok(xargs) => xargs,
Err(errno) => return request.fail_syscall(errno),
};
if xargs.flags != 0 {
return request.fail_syscall(Errno::EINVAL);
}
let flags = match to_atflags(
req.data.args[2],
AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH,
) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
let len = match to_len_cap(xargs.size.into(), XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[3], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let val = xargs.value;
if len != 0 && val != 0 && !is_valid_ptr(val, req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[3]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let mut fsflags = FsFlags::MUST_PATH;
if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
fsflags.insert(FsFlags::NO_FOLLOW_LAST);
}
let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
if empty_path {
match to_valid_fd(req.data.args[0]) {
Ok(dirfd) if dirfd != libc::AT_FDCWD => match request.get_fd(dirfd) {
Ok(fd) => match fd_status_flags(&fd) {
Ok(flags) if flags.contains(OFlag::O_PATH) => {
return request.fail_syscall(Errno::EBADF);
}
Ok(_) => {}
Err(errno) => return request.fail_syscall(errno),
},
Err(errno) => return request.fail_syscall(errno),
},
Ok(_) => {}
Err(errno) => return request.fail_syscall(errno),
}
}
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
flags: if empty_path {
SysFlags::EMPTY_PATH | SysFlags::MAYBE_NULL
} else {
SysFlags::empty()
},
fsflags,
}];
syscall_path_handler(
request,
"getxattrat",
argv,
|path_args, request, sandbox| {
syscall_getxattrat_handler(request, &sandbox, path_args, &name, val, len)
},
)
}
pub(crate) fn sys_setxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let flags = match to_xattr_flags(req.data.args[4]) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let len = match to_len_val(req.data.args[3], XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if len != 0 && req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(request, "setxattr", argv, |path_args, request, sandbox| {
syscall_setxattr_handler(request, &sandbox, path_args, &name, len, flags, true)
})
}
pub(crate) fn sys_fsetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let flags = match to_xattr_flags(req.data.args[4]) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let len = match to_len_val(req.data.args[3], XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if len != 0 && req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
dirfd: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(request, "fsetxattr", argv, |path_args, request, sandbox| {
syscall_setxattr_handler(request, &sandbox, path_args, &name, len, flags, false)
})
}
pub(crate) fn sys_lsetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let flags = match to_xattr_flags(req.data.args[4]) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let len = match to_len_val(req.data.args[3], XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if len != 0 && req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST,
..Default::default()
}];
syscall_path_handler(request, "lsetxattr", argv, |path_args, request, sandbox| {
syscall_lsetxattr_handler(request, &sandbox, path_args, &name, len, flags)
})
}
pub(crate) fn sys_setxattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let xargs = match request.remote_xattr_args(req.data.args[4], req.data.args[5]) {
Ok(xargs) => xargs,
Err(errno) => return request.fail_syscall(errno),
};
let xflags = match to_xattr_flags(xargs.flags.into()) {
Ok(xflags) => xflags,
Err(errno) => return request.fail_syscall(errno),
};
let flags = match to_atflags(
req.data.args[2],
AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH,
) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
if !is_valid_ptr(req.data.args[3], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[3]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let len = match to_len_val(xargs.size.into(), XATTR_SIZE_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
let val = xargs.value;
if len != 0 && val != 0 && !is_valid_ptr(val, req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let mut fsflags = FsFlags::MUST_PATH;
if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
fsflags.insert(FsFlags::NO_FOLLOW_LAST);
}
let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
if empty_path {
match to_valid_fd(req.data.args[0]) {
Ok(dirfd) if dirfd != libc::AT_FDCWD => match request.get_fd(dirfd) {
Ok(fd) => match fd_status_flags(&fd) {
Ok(flags) if flags.contains(OFlag::O_PATH) => {
return request.fail_syscall(Errno::EBADF);
}
Ok(_) => {}
Err(errno) => return request.fail_syscall(errno),
},
Err(errno) => return request.fail_syscall(errno),
},
Ok(_) => {}
Err(errno) => return request.fail_syscall(errno),
}
}
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
flags: if empty_path {
SysFlags::EMPTY_PATH | SysFlags::MAYBE_NULL
} else {
SysFlags::empty()
},
fsflags,
}];
syscall_path_handler(
request,
"setxattrat",
argv,
|path_args, request, sandbox| {
syscall_setxattrat_handler(request, &sandbox, path_args, &name, xflags, val, len)
},
)
}
pub(crate) fn sys_flistxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let len = match to_len_cap(req.data.args[2], XATTR_LIST_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if len != 0 && req.data.args[1] != 0 && !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
dirfd: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(
request,
"flistxattr",
argv,
|path_args, request, sandbox| {
syscall_listxattr_handler(request, &sandbox, path_args, len, false)
},
)
}
pub(crate) fn sys_listxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let len = match to_len_cap(req.data.args[2], XATTR_LIST_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if len != 0 && req.data.args[1] != 0 && !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(request, "listxattr", argv, |path_args, request, sandbox| {
syscall_listxattr_handler(request, &sandbox, path_args, len, true)
})
}
pub(crate) fn sys_llistxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let len = match to_len_cap(req.data.args[2], XATTR_LIST_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if len != 0 && req.data.args[1] != 0 && !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST,
..Default::default()
}];
syscall_path_handler(
request,
"llistxattr",
argv,
|path_args, request, sandbox| syscall_llistxattr_handler(request, &sandbox, path_args, len),
)
}
pub(crate) fn sys_removexattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(
request,
"removexattr",
argv,
|path_args, request, sandbox| {
syscall_removexattr_handler(request, &sandbox, path_args, &name, true)
},
)
}
pub(crate) fn sys_listxattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let flags = match to_atflags(
req.data.args[2],
AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH,
) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
let len = match to_len_cap(req.data.args[4], XATTR_LIST_MAX) {
Ok(len) => len,
Err(errno) => return request.fail_syscall(errno),
};
if len != 0 && req.data.args[3] != 0 && !is_valid_ptr(req.data.args[3], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let mut fsflags = FsFlags::MUST_PATH;
if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
fsflags.insert(FsFlags::NO_FOLLOW_LAST);
}
let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
if empty_path {
let dirfd = match to_fd(req.data.args[0]) {
Ok(fd) => fd,
Err(errno) => return request.fail_syscall(errno),
};
match request.get_fd(dirfd) {
Ok(fd) => match fd_status_flags(&fd) {
Ok(flags) if flags.contains(OFlag::O_PATH) => {
return request.fail_syscall(Errno::EBADF);
}
Ok(_) => {}
Err(errno) => return request.fail_syscall(errno),
},
Err(errno) => return request.fail_syscall(errno),
}
}
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
flags: if empty_path {
SysFlags::EMPTY_PATH | SysFlags::MAYBE_NULL
} else {
SysFlags::empty()
},
fsflags,
}];
syscall_path_handler(
request,
"listxattrat",
argv,
|path_args, request, sandbox| {
syscall_listxattrat_handler(request, &sandbox, path_args, len)
},
)
}
pub(crate) fn sys_fremovexattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let argv = &[SysArg {
dirfd: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(
request,
"fremovexattr",
argv,
|path_args, request, sandbox| {
syscall_removexattr_handler(request, &sandbox, path_args, &name, false)
},
)
}
pub(crate) fn sys_lremovexattr(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
if !is_valid_ptr(req.data.args[1], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[1]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST,
..Default::default()
}];
syscall_path_handler(
request,
"lremovexattr",
argv,
|path_args, request, sandbox| {
syscall_lremovexattr_handler(request, &sandbox, path_args, &name)
},
)
}
pub(crate) fn sys_removexattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
let flags = match to_xattrat_flags(req.data.args[2]) {
Ok(flags) => flags,
Err(errno) => return request.fail_syscall(errno),
};
let req = request.scmpreq;
if !is_valid_ptr(req.data.args[3], req.data.arch) {
return request.fail_syscall(Errno::EFAULT);
}
let name = match request.read_xattr(req.data.args[3]) {
Ok(name) => name,
Err(errno) => return request.fail_syscall(errno),
};
let mut fsflags = FsFlags::MUST_PATH;
if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
fsflags.insert(FsFlags::NO_FOLLOW_LAST);
}
let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
if empty_path {
let dirfd = match to_fd(req.data.args[0]) {
Ok(fd) => fd,
Err(errno) => return request.fail_syscall(errno),
};
match request.get_fd(dirfd) {
Ok(fd) => match fd_status_flags(&fd) {
Ok(flags) if flags.contains(OFlag::O_PATH) => {
return request.fail_syscall(Errno::EBADF);
}
Ok(_) => {}
Err(errno) => return request.fail_syscall(errno),
},
Err(errno) => return request.fail_syscall(errno),
}
}
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
flags: if empty_path {
SysFlags::EMPTY_PATH | SysFlags::MAYBE_NULL
} else {
SysFlags::empty()
},
fsflags,
}];
syscall_path_handler(
request,
"removexattrat",
argv,
|path_args, request, sandbox| {
syscall_removexattrat_handler(request, &sandbox, path_args, &name)
},
)
}
fn syscall_getxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
len: usize,
reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let mut fd = Cow::Borrowed(path.dir());
if reopen {
let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
let mut buf = if len > 0 {
let mut buf: Vec<u8> = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
request.cache.add_sys_block(req, false)?;
let result = safe_fgetxattr(fd.as_fd(), name, buf.as_mut());
request.cache.del_sys_block(req.id)?;
let n = result?;
if let Some(buf) = buf {
request.write_mem_all(&buf, req.data.args[2])?;
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(n as i64))
}
fn syscall_lgetxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
len: usize,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
match path.typ.as_ref() {
None => return Err(Errno::ENOENT),
Some(typ) if typ.is_symlink() || typ.is_magic_link() => return Err(Errno::EOPNOTSUPP),
_ => {}
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
let mut buf = if len > 0 {
let mut buf: Vec<u8> = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_fgetxattr(fd.as_fd(), name, buf.as_mut()),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
let n = result?;
if let Some(buf) = buf {
request.write_mem_all(&buf, req.data.args[2])?;
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(n as i64))
}
fn syscall_getxattrat_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
val: u64,
len: usize,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
if path.is_symlink() || path.is_magic_link() {
return Err(Errno::EOPNOTSUPP);
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
let mut buf = if len > 0 {
let mut buf: Vec<u8> = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_fgetxattr(fd.as_fd(), name, buf.as_mut()),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
let n = result?;
if let Some(buf) = buf {
request.write_mem_all(&buf, val)?;
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(n as i64))
}
fn syscall_lsetxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
len: usize,
flags: c_int,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
match path.typ.as_ref() {
None => return Err(Errno::ENOENT),
Some(typ) if typ.is_symlink() || typ.is_magic_link() => return Err(Errno::EOPNOTSUPP),
_ => {}
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
let val = if len > 0 {
Some(request.read_vec_all(req.data.args[2], len)?)
} else {
None
};
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_fsetxattr(fd.as_fd(), name, val.as_deref(), flags),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}
fn syscall_setxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
len: usize,
flags: c_int,
reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let mut fd = Cow::Borrowed(path.dir());
if reopen {
let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name)?;
}
let val = if len > 0 {
Some(request.read_vec_all(req.data.args[2], len)?)
} else {
None
};
request.cache.add_sys_block(req, false)?;
let result = safe_fsetxattr(fd.as_fd(), name, val.as_deref(), flags);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}
fn syscall_setxattrat_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
flags: c_int,
val: u64,
len: usize,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
if path.is_symlink() || path.is_magic_link() {
return Err(Errno::EOPNOTSUPP);
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name)?;
}
let val = if len > 0 {
Some(request.read_vec_all(val, len)?)
} else {
None
};
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_fsetxattr(fd.as_fd(), name, val.as_deref(), flags),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}
fn syscall_listxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
len: usize,
reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let mut fd = Cow::Borrowed(path.dir());
if reopen {
let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
}
let mut buf = if len > 0 {
let mut buf = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let req = request.scmpreq;
request.cache.add_sys_block(req, false)?;
let result = safe_flistxattr(fd.as_fd(), buf.as_mut());
request.cache.del_sys_block(req.id)?;
let mut n = result?;
if let Some(buf) = buf {
let buf = if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
Cow::Owned(filterxattr(&buf, n)?)
} else {
Cow::Borrowed(&buf)
};
n = buf.len();
request.write_mem_all(&buf, req.data.args[1])?;
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(n as i64))
}
fn syscall_llistxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
len: usize,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
match path.typ.as_ref() {
None => return Err(Errno::ENOENT),
Some(typ) if typ.is_symlink() || typ.is_magic_link() => return Err(Errno::EOPNOTSUPP),
_ => {}
}
let mut buf = if len > 0 {
let mut buf = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
let req = request.scmpreq;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_flistxattr(fd.as_fd(), buf.as_mut()),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
let mut n = result?;
if let Some(buf) = buf {
let buf = if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
Cow::Owned(filterxattr(&buf, n)?)
} else {
Cow::Borrowed(&buf)
};
n = buf.len();
request.write_mem_all(&buf, req.data.args[1])?;
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(n as i64))
}
fn syscall_listxattrat_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
len: usize,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
if path.is_symlink() || path.is_magic_link() {
return Err(Errno::EOPNOTSUPP);
}
let mut buf = if len > 0 {
let mut buf = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
let req = request.scmpreq;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_flistxattr(fd.as_fd(), buf.as_mut()),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
let mut n = result?;
if let Some(buf) = buf {
let buf = if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
Cow::Owned(filterxattr(&buf, n)?)
} else {
Cow::Borrowed(&buf)
};
n = buf.len();
request.write_mem_all(&buf, req.data.args[3])?;
}
#[expect(clippy::cast_possible_wrap)]
Ok(request.return_syscall(n as i64))
}
fn syscall_removexattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let mut fd = Cow::Borrowed(path.dir());
if reopen {
let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
request.cache.add_sys_block(req, false)?;
let result = safe_fremovexattr(fd.as_fd(), name);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}
fn syscall_lremovexattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
match path.typ.as_ref() {
None => return Err(Errno::ENOENT),
Some(typ) if typ.is_symlink() || typ.is_magic_link() => return Err(Errno::EOPNOTSUPP),
_ => {}
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_fremovexattr(fd.as_fd(), name),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}
fn syscall_removexattrat_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
if path.is_symlink() || path.is_magic_link() {
return Err(Errno::EOPNOTSUPP);
}
let req = request.scmpreq;
if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
let pfd = XPathBuf::from_self_fd(path.dir().as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
request.cache.add_sys_block(req, false)?;
let result = match safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty()) {
Ok(fd) => safe_fremovexattr(fd.as_fd(), name),
Err(errno) => Err(errno),
};
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}
fn to_xattr_flags(arg: u64) -> Result<c_int, Errno> {
#[expect(clippy::cast_possible_truncation)]
let flags = arg as libc::c_int;
if flags & !(XATTR_CREATE | XATTR_REPLACE) != 0 {
return Err(Errno::EINVAL);
}
Ok(flags)
}
fn to_xattrat_flags(arg: u64) -> Result<AtFlags, Errno> {
to_atflags(arg, AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH)
}
fn to_len_cap(arg: u64, max: usize) -> Result<usize, Errno> {
Ok(usize::try_from(arg).or(Err(Errno::E2BIG))?.min(max))
}
fn to_len_val(arg: u64, max: usize) -> Result<usize, Errno> {
match usize::try_from(arg).or(Err(Errno::E2BIG)) {
Ok(len) if len <= max => Ok(len),
_ => Err(Errno::E2BIG),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_xattr_flags_0() {
assert_eq!(to_xattr_flags(0), Ok(0));
}
#[test]
fn test_to_xattr_flags_1() {
assert_eq!(to_xattr_flags(XATTR_CREATE as u64), Ok(XATTR_CREATE));
}
#[test]
fn test_to_xattr_flags_2() {
assert_eq!(to_xattr_flags(XATTR_REPLACE as u64), Ok(XATTR_REPLACE));
}
#[test]
fn test_to_xattr_flags_3() {
assert_eq!(
to_xattr_flags((XATTR_CREATE | XATTR_REPLACE) as u64),
Ok(XATTR_CREATE | XATTR_REPLACE),
);
}
#[test]
fn test_to_xattr_flags_4() {
assert_eq!(to_xattr_flags(0x80), Err(Errno::EINVAL));
}
#[test]
fn test_to_xattr_flags_5() {
assert_eq!(
to_xattr_flags((XATTR_CREATE | 0x80) as u64),
Err(Errno::EINVAL),
);
}
#[test]
fn test_to_len_cap_0() {
assert_eq!(to_len_cap(0, 1024), Ok(0));
}
#[test]
fn test_to_len_cap_1() {
assert_eq!(to_len_cap(512, 1024), Ok(512));
}
#[test]
fn test_to_len_cap_2() {
assert_eq!(to_len_cap(1024, 1024), Ok(1024));
}
#[test]
fn test_to_len_cap_3() {
assert_eq!(to_len_cap(2048, 1024), Ok(1024));
}
#[test]
fn test_to_len_cap_4() {
assert_eq!(
to_len_cap(XATTR_SIZE_MAX as u64, XATTR_SIZE_MAX),
Ok(XATTR_SIZE_MAX)
);
}
#[test]
fn test_to_len_cap_5() {
assert_eq!(
to_len_cap(XATTR_SIZE_MAX as u64 + 1, XATTR_SIZE_MAX),
Ok(XATTR_SIZE_MAX),
);
}
#[test]
fn test_to_len_val_0() {
assert_eq!(to_len_val(0, 1024), Ok(0));
}
#[test]
fn test_to_len_val_1() {
assert_eq!(to_len_val(512, 1024), Ok(512));
}
#[test]
fn test_to_len_val_2() {
assert_eq!(to_len_val(1024, 1024), Ok(1024));
}
#[test]
fn test_to_len_val_3() {
assert_eq!(to_len_val(1025, 1024), Err(Errno::E2BIG));
}
#[test]
fn test_to_len_val_4() {
assert_eq!(
to_len_val(XATTR_SIZE_MAX as u64, XATTR_SIZE_MAX),
Ok(XATTR_SIZE_MAX)
);
}
#[test]
fn test_to_len_val_5() {
assert_eq!(
to_len_val(XATTR_SIZE_MAX as u64 + 1, XATTR_SIZE_MAX),
Err(Errno::E2BIG),
);
}
}