99//! [`cap_std::fs::Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html
1010
1111use cap_primitives:: fs:: FileType ;
12+ #[ cfg( unix) ]
13+ use cap_primitives:: fs:: MetadataExt ;
1214use cap_std:: fs:: { Dir , File , Metadata } ;
1315use cap_tempfile:: cap_std;
1416use cap_tempfile:: cap_std:: fs:: DirEntry ;
@@ -172,25 +174,26 @@ pub trait CapStdExtDirExt {
172174 /// require a higher level wrapper which queries the existing file and gathers such metadata
173175 /// before replacement.
174176 ///
175- /// # Example, including setting permissions
177+ /// # Security
176178 ///
177- /// The closure may also perform other file operations beyond writing, such as changing
178- /// file permissions:
179+ /// On filesystems that do not support `O_TMPFILE` (e.g. `vfat`), the TempFile is first created
180+ /// with default permissions (mode 0o666 & ~umask) that can be readeable by everyone.
181+ /// If this is a concern, you must set a more restrictive umask for your program.
182+ ///
183+ /// # Example
179184 ///
180185 /// ```rust
181186 /// # use std::io;
182187 /// # use std::io::Write;
183188 /// # use cap_tempfile::cap_std;
189+ /// # use cap_std::fs::PermissionsExt;
184190 /// # fn main() -> io::Result<()> {
185191 /// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
186192 /// use cap_std_ext::prelude::*;
187193 /// let contents = b"hello world\n";
188- /// somedir.atomic_replace_with("somefilename", |f| -> io::Result<_> {
194+ /// let perms = cap_std::fs::Permissions::from_mode(0o600);
195+ /// somedir.atomic_replace_with("somefilename", Some(perms), |f| -> io::Result<_> {
189196 /// f.write_all(contents)?;
190- /// f.flush()?;
191- /// use cap_std::fs::PermissionsExt;
192- /// let perms = cap_std::fs::Permissions::from_mode(0o600);
193- /// f.get_mut().as_file_mut().set_permissions(perms)?;
194197 /// Ok(())
195198 /// })
196199 /// # }
@@ -200,6 +203,7 @@ pub trait CapStdExtDirExt {
200203 fn atomic_replace_with < F , T , E > (
201204 & self ,
202205 destname : impl AsRef < Path > ,
206+ perms : Option < cap_std:: fs:: Permissions > ,
203207 f : F ,
204208 ) -> std:: result:: Result < T , E >
205209 where
@@ -318,26 +322,26 @@ pub trait CapStdExtDirExtUtf8 {
318322 /// require a higher level wrapper which queries the existing file and gathers such metadata
319323 /// before replacement.
320324 ///
321- /// # Example, including setting permissions
325+ /// # Security
326+ ///
327+ /// On filesystems that do not support `O_TMPFILE` (e.g. `vfat`), the TempFile is first created
328+ /// with default permissions (mode 0o666 & ~umask) that can be readeable by everyone.
329+ /// If this is a concern, you must set a more restrictive umask for your program.
322330 ///
323- /// The closure may also perform other file operations beyond writing, such as changing
324- /// file permissions:
331+ /// # Example
325332 ///
326333 /// ```rust
327334 /// # use std::io;
328335 /// # use std::io::Write;
329336 /// # use cap_tempfile::cap_std;
337+ /// # use cap_std::fs::PermissionsExt;
330338 /// # fn main() -> io::Result<()> {
331339 /// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
332- /// # let somedir = cap_std::fs_utf8::Dir::from_cap_std((&*somedir).try_clone()?);
333340 /// use cap_std_ext::prelude::*;
334341 /// let contents = b"hello world\n";
335- /// somedir.atomic_replace_with("somefilename", |f| -> io::Result<_> {
342+ /// let perms = cap_std::fs::Permissions::from_mode(0o600);
343+ /// somedir.atomic_replace_with("somefilename", Some(perms), |f| -> io::Result<_> {
336344 /// f.write_all(contents)?;
337- /// f.flush()?;
338- /// use cap_std::fs::PermissionsExt;
339- /// let perms = cap_std::fs::Permissions::from_mode(0o600);
340- /// f.get_mut().as_file_mut().set_permissions(perms)?;
341345 /// Ok(())
342346 /// })
343347 /// # }
@@ -347,6 +351,7 @@ pub trait CapStdExtDirExtUtf8 {
347351 fn atomic_replace_with < F , T , E > (
348352 & self ,
349353 destname : impl AsRef < Utf8Path > ,
354+ perms : Option < cap_std:: fs:: Permissions > ,
350355 f : F ,
351356 ) -> std:: result:: Result < T , E >
352357 where
@@ -710,6 +715,7 @@ impl CapStdExtDirExt for Dir {
710715 fn atomic_replace_with < F , T , E > (
711716 & self ,
712717 destname : impl AsRef < Path > ,
718+ perms : Option < cap_std:: fs:: Permissions > ,
713719 f : F ,
714720 ) -> std:: result:: Result < T , E >
715721 where
@@ -718,23 +724,47 @@ impl CapStdExtDirExt for Dir {
718724 {
719725 let destname = destname. as_ref ( ) ;
720726 let ( d, name) = subdir_of ( self , destname) ?;
721- let existing_metadata = d. symlink_metadata_optional ( destname) ?;
722- // If the target is already a file, then acquire its mode, which we will preserve by default.
723- // We don't follow symlinks here for replacement, and so we definitely don't want to pick up its mode.
724- let existing_perms = existing_metadata
725- . filter ( |m| m. is_file ( ) )
726- . map ( |m| m. permissions ( ) ) ;
727+
728+ // cap_tempfile doesn't support passing permissions on creation.
729+ // https://github.com/bytecodealliance/cap-std/pull/390
727730 let mut t = cap_tempfile:: TempFile :: new ( & d) ?;
728- // Apply the permissions, if we have them
729- if let Some ( existing_perms) = existing_perms {
730- t. as_file_mut ( ) . set_permissions ( existing_perms) ?;
731+ #[ cfg( unix) ]
732+ let t_metadata = t. as_file ( ) . metadata ( ) ?;
733+ #[ cfg( unix) ]
734+ if t_metadata. nlink ( ) > 0 {
735+ let zeroperms = cap_std:: fs:: PermissionsExt :: from_mode ( 0o000 ) ;
736+ t. as_file ( ) . set_permissions ( zeroperms) ?;
731737 }
738+
732739 // We always operate in terms of buffered writes
733740 let mut bufw = std:: io:: BufWriter :: new ( t) ;
734741 // Call the provided closure to generate the file content
735742 let r = f ( & mut bufw) ?;
736743 // Flush the buffer, get the TempFile
737744 t = bufw. into_inner ( ) . map_err ( From :: from) ?;
745+ // Set the file permissions
746+ // This must be done after flushing the application buffer else Linux clears the security related bits:
747+ // https://github.com/torvalds/linux/blob/94305e83eccb3120c921cd3a015cd74731140bac/fs/inode.c#L2360
748+ if let Some ( perms) = perms {
749+ t. as_file ( ) . set_permissions ( perms) ?;
750+ } else {
751+ let existing_metadata = d. symlink_metadata_optional ( destname) ?;
752+ // If the target is already a file, then acquire its mode, which we will preserve by default.
753+ // We don't follow symlinks here for replacement, and so we definitely don't want to pick up its mode.
754+ let existing_perms = existing_metadata
755+ . filter ( |m| m. is_file ( ) )
756+ . map ( |m| m. permissions ( ) ) ;
757+ if let Some ( existing_perms) = existing_perms {
758+ // Apply the existing permissions, if we have them
759+ t. as_file ( ) . set_permissions ( existing_perms) ?;
760+ } else {
761+ // Else restore default permissions
762+ #[ cfg( unix) ]
763+ if t_metadata. nlink ( ) > 0 {
764+ t. as_file ( ) . set_permissions ( t_metadata. permissions ( ) ) ?;
765+ }
766+ }
767+ }
738768 // fsync the TempFile
739769 t. as_file ( ) . sync_all ( ) ?;
740770 // rename the TempFile
@@ -745,7 +775,7 @@ impl CapStdExtDirExt for Dir {
745775 }
746776
747777 fn atomic_write ( & self , destname : impl AsRef < Path > , contents : impl AsRef < [ u8 ] > ) -> Result < ( ) > {
748- self . atomic_replace_with ( destname, |f| f. write_all ( contents. as_ref ( ) ) )
778+ self . atomic_replace_with ( destname, None , |f| f. write_all ( contents. as_ref ( ) ) )
749779 }
750780
751781 fn atomic_write_with_perms (
@@ -754,19 +784,8 @@ impl CapStdExtDirExt for Dir {
754784 contents : impl AsRef < [ u8 ] > ,
755785 perms : cap_std:: fs:: Permissions ,
756786 ) -> Result < ( ) > {
757- self . atomic_replace_with ( destname, |f| -> io:: Result < _ > {
758- // If the user is overriding the permissions, let's make the default be
759- // writable by us but not readable by anyone else, in case it has
760- // secret data.
761- #[ cfg( unix) ]
762- {
763- use cap_std:: fs:: PermissionsExt ;
764- let perms = cap_std:: fs:: Permissions :: from_mode ( 0o600 ) ;
765- f. get_mut ( ) . as_file_mut ( ) . set_permissions ( perms) ?;
766- }
787+ self . atomic_replace_with ( destname, Some ( perms) , |f| -> io:: Result < _ > {
767788 f. write_all ( contents. as_ref ( ) ) ?;
768- f. flush ( ) ?;
769- f. get_mut ( ) . as_file_mut ( ) . set_permissions ( perms) ?;
770789 Ok ( ( ) )
771790 } )
772791 }
0 commit comments