Skip to content

Commit a04ca6b

Browse files
committed
fsxattr: use fd-based xattr operations to prevent TOCTOU
1 parent d2f5cfd commit a04ca6b

File tree

1 file changed

+155
-1
lines changed

1 file changed

+155
-1
lines changed

src/uucore/src/lib/features/fsxattr.rs

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore getxattr posix_acl_default
6+
// spell-checker:ignore getxattr posix_acl_default flistxattr fgetxattr fsetxattr
77

88
//! Set of functions to manage xattr on files and dirs
99
use itertools::Itertools;
@@ -15,6 +15,10 @@ use std::path::Path;
1515

1616
/// Copies extended attributes (xattrs) from one file or directory to another.
1717
///
18+
/// This function uses file descriptor-based operations to avoid TOCTOU vulnerabilities.
19+
/// By opening file descriptors first, the inodes are pinned, preventing the paths from
20+
/// being manipulated to point to different files during the xattr copy operation.
21+
///
1822
/// # Arguments
1923
///
2024
/// * `source` - A reference to the source path.
@@ -23,7 +27,69 @@ use std::path::Path;
2327
/// # Returns
2428
///
2529
/// A result indicating success or failure.
30+
///
31+
/// # Security
32+
///
33+
/// This implementation prevents TOCTOU attacks by using file descriptor-based xattr
34+
/// operations (flistxattr, fgetxattr, fsetxattr) instead of path-based operations.
35+
#[cfg(unix)]
2636
pub fn copy_xattrs<P: AsRef<Path>>(source: P, dest: P) -> std::io::Result<()> {
37+
use nix::fcntl::{OFlag, open};
38+
use nix::sys::stat::Mode;
39+
use std::fs::{File, OpenOptions};
40+
use xattr::FileExt;
41+
42+
let source_path = source.as_ref();
43+
let dest_path = dest.as_ref();
44+
45+
// Check file types to determine how to open them
46+
let source_meta = source_path.symlink_metadata()?;
47+
let dest_meta = dest_path.symlink_metadata()?;
48+
49+
// Open file descriptors to pin the inodes and prevent TOCTOU
50+
let (source_file, dest_file) = if source_meta.is_symlink() || dest_meta.is_symlink() {
51+
// For symlinks, use O_PATH | O_NOFOLLOW to open without following
52+
// O_PATH allows opening symlinks and getting an fd for xattr operations
53+
let source_fd = open(
54+
source_path,
55+
OFlag::O_PATH | OFlag::O_NOFOLLOW,
56+
Mode::empty(),
57+
)
58+
.map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
59+
60+
let dest_fd = open(dest_path, OFlag::O_PATH | OFlag::O_NOFOLLOW, Mode::empty())
61+
.map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
62+
63+
(File::from(source_fd), File::from(dest_fd))
64+
} else {
65+
// For regular files and directories, open normally
66+
let source_file = File::open(source_path)?;
67+
68+
let dest_file = if dest_meta.is_dir() {
69+
// For directories, open with read-only (can't open directories with O_RDWR)
70+
File::open(dest_path)?
71+
} else {
72+
// For regular files, open with read+write
73+
OpenOptions::new().read(true).write(true).open(dest_path)?
74+
};
75+
76+
(source_file, dest_file)
77+
};
78+
79+
// Use fd-based xattr operations via FileExt trait
80+
// These operations use the pinned file descriptors, preventing TOCTOU
81+
for attr_name in source_file.list_xattr()? {
82+
if let Some(value) = source_file.get_xattr(&attr_name)? {
83+
dest_file.set_xattr(&attr_name, &value)?;
84+
}
85+
}
86+
87+
Ok(())
88+
}
89+
90+
#[cfg(not(unix))]
91+
pub fn copy_xattrs<P: AsRef<Path>>(source: P, dest: P) -> std::io::Result<()> {
92+
// Non-Unix platforms: fall back to path-based operations
2793
for attr_name in xattr::list(&source)? {
2894
if let Some(value) = xattr::get(&source, &attr_name)? {
2995
xattr::set(&dest, &attr_name, &value)?;
@@ -34,14 +100,58 @@ pub fn copy_xattrs<P: AsRef<Path>>(source: P, dest: P) -> std::io::Result<()> {
34100

35101
/// Retrieves the extended attributes (xattrs) of a given file or directory.
36102
///
103+
/// This function uses file descriptor-based operations to avoid TOCTOU vulnerabilities.
104+
///
37105
/// # Arguments
38106
///
39107
/// * `source` - A reference to the path of the file or directory.
40108
///
41109
/// # Returns
42110
///
43111
/// A result containing a HashMap of attributes names and values, or an error.
112+
///
113+
/// # Security
114+
///
115+
/// This implementation prevents TOCTOU attacks by opening a file descriptor once
116+
/// and using it for all xattr operations, ensuring all operations act on the same inode.
117+
#[cfg(unix)]
44118
pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<HashMap<OsString, Vec<u8>>> {
119+
use nix::fcntl::{OFlag, open};
120+
use nix::sys::stat::Mode;
121+
use std::fs::File;
122+
use xattr::FileExt;
123+
124+
let source_path = source.as_ref();
125+
let source_meta = source_path.symlink_metadata()?;
126+
127+
// Open file descriptor to pin the inode and prevent TOCTOU
128+
let source_file = if source_meta.is_symlink() {
129+
// For symlinks, use O_PATH | O_NOFOLLOW
130+
let source_fd = open(
131+
source_path,
132+
OFlag::O_PATH | OFlag::O_NOFOLLOW,
133+
Mode::empty(),
134+
)
135+
.map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
136+
File::from(source_fd)
137+
} else {
138+
// For regular files and directories
139+
File::open(source_path)?
140+
};
141+
142+
// Use fd-based operations
143+
let mut attrs = HashMap::new();
144+
for attr_name in source_file.list_xattr()? {
145+
if let Some(value) = source_file.get_xattr(&attr_name)? {
146+
attrs.insert(attr_name, value);
147+
}
148+
}
149+
Ok(attrs)
150+
}
151+
152+
#[cfg(not(unix))]
153+
pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<HashMap<OsString, Vec<u8>>> {
154+
// Non-Unix platforms: fall back to path-based operations
45155
let mut attrs = HashMap::new();
46156
for attr_name in xattr::list(&source)? {
47157
if let Some(value) = xattr::get(&source, &attr_name)? {
@@ -53,6 +163,8 @@ pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<HashMap<OsS
53163

54164
/// Applies extended attributes (xattrs) to a given file or directory.
55165
///
166+
/// This function uses file descriptor-based operations to avoid TOCTOU vulnerabilities.
167+
///
56168
/// # Arguments
57169
///
58170
/// * `dest` - A reference to the path of the file or directory.
@@ -61,10 +173,52 @@ pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<HashMap<OsS
61173
/// # Returns
62174
///
63175
/// A result indicating success or failure.
176+
///
177+
/// # Security
178+
///
179+
/// This implementation prevents TOCTOU attacks by opening a file descriptor once
180+
/// and using it for all xattr operations, ensuring all operations act on the same inode.
181+
#[cfg(unix)]
182+
pub fn apply_xattrs<P: AsRef<Path>>(
183+
dest: P,
184+
xattrs: HashMap<OsString, Vec<u8>>,
185+
) -> std::io::Result<()> {
186+
use nix::fcntl::{OFlag, open};
187+
use nix::sys::stat::Mode;
188+
use std::fs::{File, OpenOptions};
189+
use xattr::FileExt;
190+
191+
let dest_path = dest.as_ref();
192+
let dest_meta = dest_path.symlink_metadata()?;
193+
194+
// Open file descriptor to pin the inode and prevent TOCTOU
195+
let dest_file = if dest_meta.is_symlink() {
196+
// For symlinks, use O_PATH | O_NOFOLLOW
197+
let dest_fd = open(dest_path, OFlag::O_PATH | OFlag::O_NOFOLLOW, Mode::empty())
198+
.map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
199+
File::from(dest_fd)
200+
} else if dest_meta.is_dir() {
201+
// For directories, open with read-only (can't open directories with O_RDWR)
202+
// Setting xattrs works with O_RDONLY if we have write permissions on the directory
203+
File::open(dest_path)?
204+
} else {
205+
// For regular files, open with read+write
206+
OpenOptions::new().read(true).write(true).open(dest_path)?
207+
};
208+
209+
// Use fd-based operations
210+
for (attr, value) in xattrs {
211+
dest_file.set_xattr(&attr, &value)?;
212+
}
213+
Ok(())
214+
}
215+
216+
#[cfg(not(unix))]
64217
pub fn apply_xattrs<P: AsRef<Path>>(
65218
dest: P,
66219
xattrs: HashMap<OsString, Vec<u8>>,
67220
) -> std::io::Result<()> {
221+
// Non-Unix platforms: fall back to path-based operations
68222
for (attr, value) in xattrs {
69223
xattr::set(&dest, &attr, &value)?;
70224
}

0 commit comments

Comments
 (0)