#![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::{XATTR_LIST_MAX, XATTR_SIZE_MAX},
config::HAVE_XATTRAT,
confine::{
is_valid_ptr,
SydSys::{
SysFgetxattr, SysFlistxattr, SysFremovexattr, SysFsetxattr, SysGetxattr, SysGetxattrat,
SysLgetxattr, SysListxattr, SysListxattrat, SysLlistxattr, SysLremovexattr,
SysLsetxattr, SysRemovexattr, SysRemovexattrat, SysSetxattr, SysSetxattrat,
},
},
cookie::{
safe_fgetxattr, safe_flistxattr, safe_fremovexattr, safe_fsetxattr, safe_getxattr,
safe_getxattrat, safe_listxattr, safe_listxattrat, safe_removexattr, safe_removexattrat,
safe_setxattr, safe_setxattrat,
},
fd::{fd_status_flags, to_fd, to_valid_fd, PROC_FILE},
kernel::{syscall_path_handler, to_atflags},
lookup::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, SysGetxattr, argv, |path_args, request, sandbox| {
syscall_getxattr_handler(request, &sandbox, path_args, &name, len, false)
})
}
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,
SysLgetxattr,
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),
flags: SysFlags::PASS_DELETE,
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(
request,
SysFgetxattr,
argv,
|path_args, request, sandbox| {
syscall_getxattr_handler(request, &sandbox, path_args, &name, len, true)
},
)
}
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,
SysGetxattrat,
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, SysSetxattr, argv, |path_args, request, sandbox| {
syscall_setxattr_handler(request, &sandbox, path_args, &name, len, flags, false)
})
}
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),
flags: SysFlags::PASS_DELETE,
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(
request,
SysFsetxattr,
argv,
|path_args, request, sandbox| {
syscall_setxattr_handler(request, &sandbox, path_args, &name, len, flags, true)
},
)
}
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,
SysLsetxattr,
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 | SysFlags::PASS_DELETE
} else {
SysFlags::empty()
},
fsflags,
}];
syscall_path_handler(
request,
SysSetxattrat,
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),
flags: SysFlags::PASS_DELETE,
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(
request,
SysFlistxattr,
argv,
|path_args, request, sandbox| {
syscall_listxattr_handler(request, &sandbox, path_args, len, true)
},
)
}
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,
SysListxattr,
argv,
|path_args, request, sandbox| {
syscall_listxattr_handler(request, &sandbox, path_args, len, false)
},
)
}
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,
SysLlistxattr,
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,
SysRemovexattr,
argv,
|path_args, request, sandbox| {
syscall_removexattr_handler(request, &sandbox, path_args, &name, false)
},
)
}
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 | SysFlags::PASS_DELETE
} else {
SysFlags::empty()
},
fsflags,
}];
syscall_path_handler(
request,
SysListxattrat,
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),
flags: SysFlags::PASS_DELETE,
fsflags: FsFlags::MUST_PATH,
..Default::default()
}];
syscall_path_handler(
request,
SysFremovexattr,
argv,
|path_args, request, sandbox| {
syscall_removexattr_handler(request, &sandbox, path_args, &name, true)
},
)
}
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,
SysLremovexattr,
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 | SysFlags::PASS_DELETE
} else {
SysFlags::empty()
},
fsflags,
}];
syscall_path_handler(
request,
SysRemovexattrat,
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,
is_fd: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let req = request.scmpreq;
if !sandbox.options.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_exact(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let n = if is_fd {
safe_fgetxattr(path.dir(), name, buf.as_mut())?
} else {
proc_getxattr(path.dir(), name, buf.as_mut())?
};
if len > 0 && n > len {
return Err(Errno::ERANGE);
}
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::ENODATA),
_ => {}
}
let req = request.scmpreq;
if !sandbox.options.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_exact(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let n = proc_getxattr(path.dir(), name, buf.as_mut())?;
if len > 0 && n > len {
return Err(Errno::ERANGE);
}
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;
match path.typ.as_ref() {
None => return Err(Errno::ENOENT),
Some(typ) if typ.is_symlink() || typ.is_magic_link() => return Err(Errno::ENODATA),
_ => {}
}
let req = request.scmpreq;
if !sandbox.options.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_exact(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let n = proc_getxattr(path.dir(), name, buf.as_mut())?;
if len > 0 && n > len {
return Err(Errno::ERANGE);
}
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::EPERM),
_ => {}
}
let req = request.scmpreq;
if !sandbox.options.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
};
proc_setxattr(path.dir(), name, val.as_deref(), flags).map(|_| request.return_syscall(0))
}
fn syscall_setxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
name: &CStr,
len: usize,
flags: c_int,
is_fd: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let req = request.scmpreq;
if !sandbox.options.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
};
if is_fd {
safe_fsetxattr(path.dir(), name, val.as_deref(), flags)
} else {
proc_setxattr(path.dir(), name, val.as_deref(), flags)
}
.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::EPERM);
}
let req = request.scmpreq;
if !sandbox.options.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name)?;
}
let val = if len > 0 {
Some(request.read_vec_all(val, len)?)
} else {
None
};
proc_setxattr(path.dir(), name, val.as_deref(), flags).map(|_| request.return_syscall(0))
}
fn syscall_listxattr_handler(
request: &UNotifyEventRequest,
sandbox: &SandboxGuard,
args: PathArgs,
len: usize,
is_fd: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let mut buf = if len > 0 {
let mut buf = Vec::new();
buf.try_reserve_exact(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let mut n = if is_fd {
safe_flistxattr(path.dir(), buf.as_mut())?
} else {
proc_listxattr(path.dir(), buf.as_mut())?
};
if let Some(buf) = buf {
let req = request.scmpreq;
let buf = if !sandbox.options.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
Cow::Owned(filterxattr(&buf, n)?)
} else {
Cow::Borrowed(&buf)
};
n = buf.len();
if n > len {
return Err(Errno::ERANGE);
}
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 Ok(request.return_syscall(0));
}
_ => {}
}
let mut buf = if len > 0 {
let mut buf = Vec::new();
buf.try_reserve_exact(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let mut n = proc_listxattr(path.dir(), buf.as_mut())?;
if let Some(buf) = buf {
let req = request.scmpreq;
let buf = if !sandbox.options.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
Cow::Owned(filterxattr(&buf, n)?)
} else {
Cow::Borrowed(&buf)
};
n = buf.len();
if n > len {
return Err(Errno::ERANGE);
}
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;
match path.typ.as_ref() {
None => return Err(Errno::ENOENT),
Some(typ) if typ.is_symlink() || typ.is_magic_link() => {
return Ok(request.return_syscall(0));
}
_ => {}
}
let mut buf = if len > 0 {
let mut buf = Vec::new();
buf.try_reserve_exact(len).or(Err(Errno::ENOMEM))?;
Some(buf)
} else {
None
};
let mut n = proc_listxattr(path.dir(), buf.as_mut())?;
if let Some(buf) = buf {
let req = request.scmpreq;
let buf = if !sandbox.options.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
Cow::Owned(filterxattr(&buf, n)?)
} else {
Cow::Borrowed(&buf)
};
n = buf.len();
if n > len {
return Err(Errno::ERANGE);
}
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,
is_fd: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
let req = request.scmpreq;
if !sandbox.options.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
if is_fd {
safe_fremovexattr(path.dir(), name)
} else {
proc_removexattr(path.dir(), name)
}
.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::EPERM),
_ => {}
}
let req = request.scmpreq;
if !sandbox.options.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
proc_removexattr(path.dir(), name).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::EPERM);
}
let req = request.scmpreq;
if !sandbox.options.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
denyxattr(name).or(Err(Errno::ENODATA))?;
}
proc_removexattr(path.dir(), name).map(|_| request.return_syscall(0))
}
fn proc_getxattr<Fd: AsFd>(
dir: Fd,
name: &CStr,
value: Option<&mut Vec<u8>>,
) -> Result<usize, Errno> {
let fd = dir.as_fd().as_raw_fd();
let pfd = if *HAVE_XATTRAT {
XPathBuf::from_self_fd(fd)? } else {
let mut pfd = XPathBuf::try_from("/proc/thread-self/fd")?;
pfd.try_push_fd(fd)?;
pfd
};
if *HAVE_XATTRAT {
safe_getxattrat(PROC_FILE(), &pfd, 0, name, value)
} else {
safe_getxattr(&pfd, name, value)
}
}
fn proc_setxattr<Fd: AsFd>(
dir: Fd,
name: &CStr,
value: Option<&[u8]>,
flags: c_int,
) -> Result<(), Errno> {
let fd = dir.as_fd().as_raw_fd();
let pfd = if *HAVE_XATTRAT {
XPathBuf::from_self_fd(fd)? } else {
let mut pfd = XPathBuf::try_from("/proc/thread-self/fd")?;
pfd.try_push_fd(fd)?;
pfd
};
if *HAVE_XATTRAT {
safe_setxattrat(PROC_FILE(), &pfd, 0, name, value, flags)
} else {
safe_setxattr(&pfd, name, value, flags)
}
}
fn proc_removexattr<Fd: AsFd>(dir: Fd, name: &CStr) -> Result<(), Errno> {
let fd = dir.as_fd().as_raw_fd();
let pfd = if *HAVE_XATTRAT {
XPathBuf::from_self_fd(fd)? } else {
let mut pfd = XPathBuf::try_from("/proc/thread-self/fd")?;
pfd.try_push_fd(fd)?;
pfd
};
if *HAVE_XATTRAT {
safe_removexattrat(PROC_FILE(), &pfd, 0, name)
} else {
safe_removexattr(&pfd, name)
}
}
fn proc_listxattr<Fd: AsFd>(dir: Fd, list: Option<&mut Vec<u8>>) -> Result<usize, Errno> {
let fd = dir.as_fd().as_raw_fd();
let pfd = if *HAVE_XATTRAT {
XPathBuf::from_self_fd(fd)? } else {
let mut pfd = XPathBuf::try_from("/proc/thread-self/fd")?;
pfd.try_push_fd(fd)?;
pfd
};
if *HAVE_XATTRAT {
safe_listxattrat(PROC_FILE(), &pfd, 0, list)
} else {
safe_listxattr(&pfd, list)
}
}
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),
);
}
}