use std::{
ffi::CStr,
os::fd::{AsFd, AsRawFd},
};
use libc::{c_int, c_void, size_t};
use memchr::{arch::all::is_prefix, memchr};
use nix::{errno::Errno, NixPath};
pub fn fgetxattr<Fd: AsFd, P: ?Sized + NixPath>(
fd: Fd,
name: &P,
value: Option<&mut [u8]>,
) -> Result<usize, Errno> {
let (value, len) = match value {
Some(v) => (v.as_mut_ptr() as *mut c_void, v.len() as size_t),
None => (std::ptr::null_mut(), 0),
};
let res = name.with_nix_path(|name_ptr| unsafe {
libc::fgetxattr(fd.as_fd().as_raw_fd(), name_ptr.as_ptr(), value, len)
})?;
#[expect(clippy::cast_sign_loss)]
Errno::result(res).map(|res| res as usize)
}
pub fn fsetxattr<Fd: AsFd, P: ?Sized + NixPath>(
fd: Fd,
name: &P,
value: &[u8],
flags: i32,
) -> Result<(), Errno> {
let res = name.with_nix_path(|name_ptr| unsafe {
libc::fsetxattr(
fd.as_fd().as_raw_fd(),
name_ptr.as_ptr(),
value.as_ptr() as *const c_void,
value.len() as size_t,
flags as c_int,
)
})?;
Errno::result(res).map(drop)
}
pub fn fremovexattr<Fd: AsFd, P: ?Sized + NixPath>(fd: Fd, name: &P) -> Result<(), Errno> {
let res = name.with_nix_path(|name_ptr| unsafe {
libc::fremovexattr(fd.as_fd().as_raw_fd(), name_ptr.as_ptr())
})?;
Errno::result(res).map(drop)
}
const XATTR_SEC: &[&[u8]] = &[b"security.", b"system.", b"trusted."];
pub fn denyxattr(name: &CStr) -> Result<(), Errno> {
let name = name.to_bytes();
for prefix in XATTR_SEC {
if is_prefix(name, prefix) {
return Err(Errno::EPERM);
}
}
Ok(())
}
pub fn filterxattr(buf: &[u8], n: usize) -> Result<Vec<u8>, Errno> {
let mut soff = 0;
let mut fbuf = Vec::new();
while soff < n {
let end = if let Some(end) = memchr(0, &buf[soff..]) {
end
} else {
break;
};
let eoff = soff
.checked_add(end)
.ok_or(Errno::EOVERFLOW)?
.checked_add(1)
.ok_or(Errno::EOVERFLOW)?;
let name = &buf[soff..eoff];
let cstr = unsafe { CStr::from_bytes_with_nul_unchecked(name) };
let cstr = cstr.to_bytes();
let mut filter = false;
for prefix in XATTR_SEC {
if is_prefix(cstr, prefix) {
filter = true;
break;
}
}
if !filter {
fbuf.try_reserve(name.len()).or(Err(Errno::ENOMEM))?;
fbuf.extend_from_slice(name);
}
soff = eoff;
}
Ok(fbuf)
}
#[cfg(test)]
mod tests {
use std::{ffi::CStr, os::fd::AsFd};
use tempfile::NamedTempFile;
use super::*;
#[test]
fn test_denyxattr_1() {
let name = CStr::from_bytes_with_nul(b"user.test\0").unwrap();
assert!(denyxattr(name).is_ok());
}
#[test]
fn test_denyxattr_2() {
let name = CStr::from_bytes_with_nul(b"system.posix_acl_access\0").unwrap();
assert_eq!(denyxattr(name), Err(Errno::EPERM));
}
#[test]
fn test_denyxattr_3() {
let name = CStr::from_bytes_with_nul(b"security.selinux\0").unwrap();
assert_eq!(denyxattr(name), Err(Errno::EPERM));
}
#[test]
fn test_denyxattr_4() {
let name = CStr::from_bytes_with_nul(b"trusted.overlay\0").unwrap();
assert_eq!(denyxattr(name), Err(Errno::EPERM));
}
#[test]
fn test_denyxattr_5() {
let name = CStr::from_bytes_with_nul(b"securitynodot\0").unwrap();
assert!(denyxattr(name).is_ok());
}
#[test]
fn test_denyxattr_6() {
let name = CStr::from_bytes_with_nul(b"security.\0").unwrap();
assert_eq!(denyxattr(name), Err(Errno::EPERM));
}
#[test]
fn test_denyxattr_7() {
let name = CStr::from_bytes_with_nul(b"trusted.\0").unwrap();
assert_eq!(denyxattr(name), Err(Errno::EPERM));
}
#[test]
fn test_filterxattr_1() {
let result = filterxattr(&[], 0).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_filterxattr_2() {
let buf = b"user.test\0user.foo\0";
let result = filterxattr(buf, buf.len()).unwrap();
assert_eq!(result, buf);
}
#[test]
fn test_filterxattr_3() {
let buf = b"security.selinux\0user.test\0";
let result = filterxattr(buf, buf.len()).unwrap();
assert_eq!(result, b"user.test\0");
}
#[test]
fn test_filterxattr_4() {
let buf = b"trusted.overlay\0user.test\0";
let result = filterxattr(buf, buf.len()).unwrap();
assert_eq!(result, b"user.test\0");
}
#[test]
fn test_filterxattr_5() {
let buf = b"security.selinux\0trusted.overlay\0user.test\0";
let result = filterxattr(buf, buf.len()).unwrap();
assert_eq!(result, b"user.test\0");
}
#[test]
fn test_filterxattr_6() {
let buf = b"security.selinux\0trusted.overlay\0";
let result = filterxattr(buf, buf.len()).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_filterxattr_7() {
let buf = b"system.posix_acl\0security.ima\0";
let result = filterxattr(buf, buf.len()).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_filterxattr_8() {
let buf = b"user.a\0security.b\0user.c\0";
let result = filterxattr(buf, 7).unwrap();
assert_eq!(result, b"user.a\0");
}
#[test]
fn test_filterxattr_9() {
let buf = b"user.test";
let result = filterxattr(buf, buf.len()).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_fgetxattr_1() {
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_file().as_fd();
let mut buf = [0u8; 256];
let result = fgetxattr(fd, c"user.test", Some(&mut buf));
assert!(result.is_err());
}
#[test]
fn test_fsetxattr_1() {
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_file().as_fd();
let value = b"hello";
let set_result = fsetxattr(fd, c"user.test", value, 0);
if set_result.is_err() {
return;
}
let mut buf = [0u8; 256];
let len = fgetxattr(fd, c"user.test", Some(&mut buf)).unwrap();
assert_eq!(&buf[..len], value);
}
#[test]
fn test_fremovexattr_1() {
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_file().as_fd();
let value = b"hello";
if fsetxattr(fd, c"user.test", value, 0).is_err() {
return;
}
fremovexattr(fd, c"user.test").unwrap();
let mut buf = [0u8; 256];
assert!(fgetxattr(fd, c"user.test", Some(&mut buf)).is_err());
}
#[test]
fn test_fgetxattr_2() {
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_file().as_fd();
let value = b"test_value";
if fsetxattr(fd, c"user.size_test", value, 0).is_err() {
return;
}
let size = fgetxattr(fd, c"user.size_test", None::<&mut [u8]>).unwrap();
assert_eq!(size, value.len());
}
}