xattr: api cleanup

Make `flistxattr()` return a `ListXAttr` helper which
provides an iterator over `&CStr`.

This exposes the property that xattr names are a
zero-terminated string without simply being an opaque
"byte vector". Using &[u8] as a type here is too lax.

Also let `fgetxattr` take a `CStr`. While this may be a
burden on the caller, we usually already have
zero-terminated strings on the call site. Currently we only
use this method coming from `flistxattr` after all.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-04-24 10:48:28 +02:00
parent 9af76ef075
commit 27a3decbfe
3 changed files with 82 additions and 28 deletions

View File

@ -287,7 +287,7 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> {
Err(err) => bail!("read_xattrs failed for {:?} - {}", self.full_path(), err),
};
for name in xattr_names.split(|c| *c == b'\0') {
for name in &xattr_names {
// Only extract the relevant extended attributes
if !xattr::is_valid_xattr_name(&name) {
continue;
@ -307,7 +307,7 @@ impl<'a, W: Write, C: BackupCatalogWriter> Encoder<'a, W, C> {
}
} else if self.has_features(flags::WITH_XATTRS) {
xattrs.push(PxarXAttr {
name: name.to_vec(),
name: name.to_bytes().to_vec(),
value,
});
}

View File

@ -1,7 +1,7 @@
//! *pxar* format decoder.
//!
//! This module contain the code to decode *pxar* archive files.
use std::ffi::CString;
use std::ffi::{CStr, CString};
use std::ffi::{OsStr, OsString};
use std::io::{Read, Write};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
@ -164,9 +164,10 @@ impl<R: Read> SequentialDecoder<R> {
.position(|c| *c == b'\0')
.ok_or_else(|| format_err!("no value found in xattr"))?;
let (name, value) = buffer.split_at(separator);
if !xattr::is_valid_xattr_name(name) || xattr::is_security_capability(name) {
bail!("incorrect xattr name - {}.", String::from_utf8_lossy(name));
let (name, value) = buffer.split_at(separator + 1);
let c_name = unsafe { CStr::from_bytes_with_nul_unchecked(name) };
if !xattr::is_valid_xattr_name(c_name) || xattr::is_security_capability(c_name) {
bail!("incorrect xattr name - {:?}.", c_name);
}
Ok(PxarXAttr {

View File

@ -1,15 +1,59 @@
//! Wrapper functions for the libc xattr calls
extern crate libc;
use std::ffi::CStr;
use std::os::unix::io::RawFd;
use nix::errno::Errno;
use proxmox::tools::vec;
use crate::pxar::{PxarXAttr, PxarFCaps};
pub fn flistxattr(fd: RawFd) -> Result<Vec<u8>, nix::errno::Errno> {
/// Result of `flistxattr`, allows iterating over the attributes as a list of `&CStr`s.
///
/// Listing xattrs produces a list separated by zeroes, inherently making them available as `&CStr`
/// already, so we make use of this fact and reflect this in the interface.
pub struct ListXAttr {
data: Vec<u8>,
}
impl ListXAttr {
fn new(data: Vec<u8>) -> Self {
Self { data }
}
}
impl<'a> IntoIterator for &'a ListXAttr {
type Item = &'a CStr;
type IntoIter = ListXAttrIter<'a>;
fn into_iter(self) -> Self::IntoIter {
ListXAttrIter {
data: &self.data,
at: 0,
}
}
}
/// Iterator over the extended attribute entries in a `ListXAttr`.
pub struct ListXAttrIter<'a> {
data: &'a [u8],
at: usize,
}
impl<'a> Iterator for ListXAttrIter<'a> {
type Item = &'a CStr;
fn next(&mut self) -> Option<&'a CStr> {
let data = &self.data[self.at..];
let next = data.iter().position(|b| *b == 0)? + 1;
self.at += next;
Some(unsafe { CStr::from_bytes_with_nul_unchecked(&data[..next]) })
}
}
/// Return a list of extended attributes accessible as an iterator over items of type `&CStr`.
pub fn flistxattr(fd: RawFd) -> Result<ListXAttr, nix::errno::Errno> {
// Initial buffer size for the attribute list, if content does not fit
// it gets dynamically increased until big enough.
let mut size = 256;
@ -32,16 +76,20 @@ pub fn flistxattr(fd: RawFd) -> Result<Vec<u8>, nix::errno::Errno> {
libc::flistxattr(fd, buffer.as_mut_ptr() as *mut i8, buffer.len())
};
}
buffer.resize(bytes as usize, 0);
buffer.truncate(bytes as usize);
Ok(buffer)
Ok(ListXAttr::new(buffer))
}
pub fn fgetxattr(fd: RawFd, name: &[u8]) -> Result<Vec<u8>, nix::errno::Errno> {
/// Get an extended attribute by name.
///
/// Extended attributes may not contain zeroes, which we enforce in the API by using a `&CStr`
/// type.
pub fn fgetxattr(fd: RawFd, name: &CStr) -> Result<Vec<u8>, nix::errno::Errno> {
let mut size = 256;
let mut buffer = vec::undefined(size);
let mut bytes = unsafe {
libc::fgetxattr(fd, name.as_ptr() as *const i8, buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
libc::fgetxattr(fd, name.as_ptr(), buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
};
while bytes < 0 {
let err = Errno::last();
@ -92,20 +140,21 @@ pub fn fsetxattr_fcaps(fd: RawFd, fcaps: &PxarFCaps) -> Result<(), nix::errno::E
Ok(())
}
pub fn is_security_capability(name: &[u8]) -> bool {
name == b"security.capability"
pub fn is_security_capability(name: &CStr) -> bool {
name.to_bytes() == b"security.capability"
}
/// Check if the passed name buffer starts with a valid xattr namespace prefix
/// and is within the length limit of 255 bytes
pub fn is_valid_xattr_name(name: &[u8]) -> bool {
pub fn is_valid_xattr_name(c_name: &CStr) -> bool {
let name = c_name.to_bytes();
if name.is_empty() || name.len() > 255 {
return false;
}
if name.starts_with(b"user.") || name.starts_with(b"trusted.") {
return true;
}
is_security_capability(name)
is_security_capability(c_name)
}
#[cfg(test)]
@ -118,6 +167,8 @@ mod tests {
#[test]
fn test_fsetxattr_fgetxattr() {
use proxmox::c_str;
let path = "./tests/xattrs.txt";
let file = OpenOptions::new()
.write(true)
@ -167,26 +218,28 @@ mod tests {
assert_eq!(fsetxattr(fd, &invalid_name_prefix), Err(Errno::EOPNOTSUPP));
assert_eq!(fsetxattr(fd, &invalid_name_length), Err(Errno::ERANGE));
let v0 = fgetxattr(fd, b"user.attribute0\0".as_ref()).unwrap();
let v1 = fgetxattr(fd, b"user.empty\0".as_ref()).unwrap();
let v0 = fgetxattr(fd, c_str!("user.attribute0")).unwrap();
let v1 = fgetxattr(fd, c_str!("user.empty")).unwrap();
assert_eq!(v0, b"value0".as_ref());
assert_eq!(v1, b"".as_ref());
assert_eq!(fgetxattr(fd, b"user.attribute1\0".as_ref()), Err(Errno::ENODATA));
assert_eq!(fgetxattr(fd, c_str!("user.attribute1")), Err(Errno::ENODATA));
std::fs::remove_file(&path).unwrap();
}
#[test]
fn test_is_valid_xattr_name() {
let empty = Vec::new();
let too_long = vec![b'a'; 265];
use std::ffi::CString;
assert!(!is_valid_xattr_name(empty.as_slice()));
assert!(!is_valid_xattr_name(too_long.as_slice()));
assert!(!is_valid_xattr_name(b"system.attr"));
assert!(is_valid_xattr_name(b"user.attr"));
assert!(is_valid_xattr_name(b"trusted.attr"));
assert!(is_valid_xattr_name(b"security.capability"));
use proxmox::c_str;
let too_long = CString::new(vec![b'a'; 265]).unwrap();
assert!(!is_valid_xattr_name(&too_long));
assert!(!is_valid_xattr_name(c_str!("system.attr")));
assert!(is_valid_xattr_name(c_str!("user.attr")));
assert!(is_valid_xattr_name(c_str!("trusted.attr")));
assert!(is_valid_xattr_name(c_str!("security.capability")));
}
}