]> git.proxmox.com Git - proxmox-backup.git/blob - src/tools/xattr.rs
1450d0c3a194b20144b27b4a62018f1ef8f8b9bc
[proxmox-backup.git] / src / tools / xattr.rs
1 //! Wrapper functions for the libc xattr calls
2
3 use std::ffi::CStr;
4 use std::os::unix::io::RawFd;
5
6 use nix::errno::Errno;
7
8 use proxmox::c_str;
9 use proxmox::tools::vec;
10
11 /// `"security.capability"` as a CStr to avoid typos.
12 ///
13 /// This cannot be `const` until `const_cstr_unchecked` is stable.
14 #[inline]
15 pub fn xattr_name_fcaps() -> &'static CStr {
16 c_str!("security.capability")
17 }
18
19 /// Result of `flistxattr`, allows iterating over the attributes as a list of `&CStr`s.
20 ///
21 /// Listing xattrs produces a list separated by zeroes, inherently making them available as `&CStr`
22 /// already, so we make use of this fact and reflect this in the interface.
23 pub struct ListXAttr {
24 data: Vec<u8>,
25 }
26
27 impl ListXAttr {
28 fn new(data: Vec<u8>) -> Self {
29 Self { data }
30 }
31 }
32
33 impl<'a> IntoIterator for &'a ListXAttr {
34 type Item = &'a CStr;
35 type IntoIter = ListXAttrIter<'a>;
36
37 fn into_iter(self) -> Self::IntoIter {
38 ListXAttrIter {
39 data: &self.data,
40 at: 0,
41 }
42 }
43 }
44
45 /// Iterator over the extended attribute entries in a `ListXAttr`.
46 pub struct ListXAttrIter<'a> {
47 data: &'a [u8],
48 at: usize,
49 }
50
51 impl<'a> Iterator for ListXAttrIter<'a> {
52 type Item = &'a CStr;
53
54 fn next(&mut self) -> Option<&'a CStr> {
55 let data = &self.data[self.at..];
56 let next = data.iter().position(|b| *b == 0)? + 1;
57 self.at += next;
58 Some(unsafe { CStr::from_bytes_with_nul_unchecked(&data[..next]) })
59 }
60 }
61
62 /// Return a list of extended attributes accessible as an iterator over items of type `&CStr`.
63 pub fn flistxattr(fd: RawFd) -> Result<ListXAttr, nix::errno::Errno> {
64 // Initial buffer size for the attribute list, if content does not fit
65 // it gets dynamically increased until big enough.
66 let mut size = 256;
67 let mut buffer = vec::undefined(size);
68 let mut bytes = unsafe {
69 libc::flistxattr(fd, buffer.as_mut_ptr() as *mut i8, buffer.len())
70 };
71 while bytes < 0 {
72 let err = Errno::last();
73 match err {
74 Errno::ERANGE => {
75 // Buffer was not big enough to fit the list, retry with double the size
76 size = size.checked_mul(2).ok_or(Errno::ENOMEM)?;
77 },
78 _ => return Err(err),
79 }
80 // Retry to read the list with new buffer
81 buffer.resize(size, 0);
82 bytes = unsafe {
83 libc::flistxattr(fd, buffer.as_mut_ptr() as *mut i8, buffer.len())
84 };
85 }
86 buffer.truncate(bytes as usize);
87
88 Ok(ListXAttr::new(buffer))
89 }
90
91 /// Get an extended attribute by name.
92 ///
93 /// Extended attributes may not contain zeroes, which we enforce in the API by using a `&CStr`
94 /// type.
95 pub fn fgetxattr(fd: RawFd, name: &CStr) -> Result<Vec<u8>, nix::errno::Errno> {
96 let mut size = 256;
97 let mut buffer = vec::undefined(size);
98 let mut bytes = unsafe {
99 libc::fgetxattr(fd, name.as_ptr(), buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
100 };
101 while bytes < 0 {
102 let err = Errno::last();
103 match err {
104 Errno::ERANGE => {
105 // Buffer was not big enough to fit the value, retry with double the size
106 size = size.checked_mul(2).ok_or(Errno::ENOMEM)?;
107 },
108 _ => return Err(err),
109 }
110 buffer.resize(size, 0);
111 bytes = unsafe {
112 libc::fgetxattr(fd, name.as_ptr() as *const i8, buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
113 };
114 }
115 buffer.resize(bytes as usize, 0);
116
117 Ok(buffer)
118 }
119
120 /// Set an extended attribute on a file descriptor.
121 pub fn fsetxattr(fd: RawFd, name: &CStr, data: &[u8]) -> Result<(), nix::errno::Errno> {
122 let flags = 0 as libc::c_int;
123 let result = unsafe {
124 libc::fsetxattr(fd, name.as_ptr(), data.as_ptr() as *const libc::c_void, data.len(), flags)
125 };
126 if result < 0 {
127 return Err(Errno::last());
128 }
129
130 Ok(())
131 }
132
133 pub fn fsetxattr_fcaps(fd: RawFd, fcaps: &[u8]) -> Result<(), nix::errno::Errno> {
134 // TODO casync checks and removes capabilities if they are set
135 fsetxattr(fd, xattr_name_fcaps(), fcaps)
136 }
137
138 pub fn is_security_capability(name: &CStr) -> bool {
139 name.to_bytes() == xattr_name_fcaps().to_bytes()
140 }
141
142 /// Check if the passed name buffer starts with a valid xattr namespace prefix
143 /// and is within the length limit of 255 bytes
144 pub fn is_valid_xattr_name(c_name: &CStr) -> bool {
145 let name = c_name.to_bytes();
146 if name.is_empty() || name.len() > 255 {
147 return false;
148 }
149 if name.starts_with(b"user.") || name.starts_with(b"trusted.") {
150 return true;
151 }
152 is_security_capability(c_name)
153 }
154
155 #[cfg(test)]
156 mod tests {
157 use super::*;
158
159 use std::ffi::CString;
160 use std::fs::OpenOptions;
161 use std::os::unix::io::AsRawFd;
162
163 use nix::errno::Errno;
164
165 use proxmox::c_str;
166
167 #[test]
168 fn test_fsetxattr_fgetxattr() {
169 let path = "./tests/xattrs.txt";
170 let file = OpenOptions::new()
171 .write(true)
172 .create(true)
173 .open(&path)
174 .unwrap();
175
176 let fd = file.as_raw_fd();
177
178 let mut name = b"user.".to_vec();
179 for _ in 0..260 {
180 name.push(b'a');
181 }
182
183 let invalid_name = CString::new(name).unwrap();
184
185 assert!(fsetxattr(fd, c_str!("user.attribute0"), b"value0").is_ok());
186 assert!(fsetxattr(fd, c_str!("user.empty"), b"").is_ok());
187
188 if nix::unistd::Uid::current() != nix::unistd::ROOT {
189 assert_eq!(fsetxattr(fd, c_str!("trusted.attribute0"), b"value0"), Err(Errno::EPERM));
190 }
191
192 assert_eq!(fsetxattr(fd, c_str!("garbage.attribute0"), b"value"), Err(Errno::EOPNOTSUPP));
193 assert_eq!(fsetxattr(fd, &invalid_name, b"err"), Err(Errno::ERANGE));
194
195 let v0 = fgetxattr(fd, c_str!("user.attribute0")).unwrap();
196 let v1 = fgetxattr(fd, c_str!("user.empty")).unwrap();
197
198 assert_eq!(v0, b"value0".as_ref());
199 assert_eq!(v1, b"".as_ref());
200 assert_eq!(fgetxattr(fd, c_str!("user.attribute1")), Err(Errno::ENODATA));
201
202 std::fs::remove_file(&path).unwrap();
203 }
204
205 #[test]
206 fn test_is_valid_xattr_name() {
207 let too_long = CString::new(vec![b'a'; 265]).unwrap();
208
209 assert!(!is_valid_xattr_name(&too_long));
210 assert!(!is_valid_xattr_name(c_str!("system.attr")));
211 assert!(is_valid_xattr_name(c_str!("user.attr")));
212 assert!(is_valid_xattr_name(c_str!("trusted.attr")));
213 assert!(is_valid_xattr_name(super::xattr_name_fcaps()));
214 }
215 }