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
99use 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) ]
2636pub 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) ]
44118pub 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) ) ]
64217pub 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