diff --git a/src/pxar/encoder.rs b/src/pxar/encoder.rs index 902248ae..bb0d0d20 100644 --- a/src/pxar/encoder.rs +++ b/src/pxar/encoder.rs @@ -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, }); } diff --git a/src/pxar/sequential_decoder.rs b/src/pxar/sequential_decoder.rs index e17fa826..950b2aa6 100644 --- a/src/pxar/sequential_decoder.rs +++ b/src/pxar/sequential_decoder.rs @@ -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 SequentialDecoder { .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 { diff --git a/src/tools/xattr.rs b/src/tools/xattr.rs index dda5fc95..f8df207d 100644 --- a/src/tools/xattr.rs +++ b/src/tools/xattr.rs @@ -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, 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, +} + +impl ListXAttr { + fn new(data: Vec) -> 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 { // 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, 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, 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, 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"))); } }