88//!
99//! [`cap_std::fs::Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html
1010
11- use cap_primitives:: fs:: FileType ;
11+ use cap_primitives:: fs:: { FileType , PermissionsExt } ;
1212use cap_std:: fs:: { Dir , File , Metadata } ;
1313use cap_tempfile:: cap_std;
1414use cap_tempfile:: cap_std:: fs:: DirEntry ;
@@ -172,25 +172,26 @@ pub trait CapStdExtDirExt {
172172 /// require a higher level wrapper which queries the existing file and gathers such metadata
173173 /// before replacement.
174174 ///
175- /// # Example, including setting permissions
175+ /// # Security
176176 ///
177- /// The closure may also perform other file operations beyond writing, such as changing
178- /// file permissions:
177+ /// On filesystems that do not support `O_TMPFILE` (e.g. `vfat`), the TempFile is first created
178+ /// with default permissions (mode 0o666 & ~umask) that can be readeable by everyone.
179+ /// If this is a concern, you must set a more restrictive umask for your program.
180+ ///
181+ /// # Example
179182 ///
180183 /// ```rust
181184 /// # use std::io;
182185 /// # use std::io::Write;
183186 /// # use cap_tempfile::cap_std;
187+ /// # use cap_std::fs::PermissionsExt;
184188 /// # fn main() -> io::Result<()> {
185189 /// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
186190 /// use cap_std_ext::prelude::*;
187191 /// let contents = b"hello world\n";
188- /// somedir.atomic_replace_with("somefilename", |f| -> io::Result<_> {
192+ /// let perms = cap_std::fs::Permissions::from_mode(0o600);
193+ /// somedir.atomic_replace_with("somefilename", Some(perms), |f| -> io::Result<_> {
189194 /// 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)?;
194195 /// Ok(())
195196 /// })
196197 /// # }
@@ -200,6 +201,7 @@ pub trait CapStdExtDirExt {
200201 fn atomic_replace_with < F , T , E > (
201202 & self ,
202203 destname : impl AsRef < Path > ,
204+ perms : Option < cap_std:: fs:: Permissions > ,
203205 f : F ,
204206 ) -> std:: result:: Result < T , E >
205207 where
@@ -318,26 +320,26 @@ pub trait CapStdExtDirExtUtf8 {
318320 /// require a higher level wrapper which queries the existing file and gathers such metadata
319321 /// before replacement.
320322 ///
321- /// # Example, including setting permissions
323+ /// # Security
324+ ///
325+ /// On filesystems that do not support `O_TMPFILE` (e.g. `vfat`), the TempFile is first created
326+ /// with default permissions (mode 0o666 & ~umask) that can be readeable by everyone.
327+ /// If this is a concern, you must set a more restrictive umask for your program.
322328 ///
323- /// The closure may also perform other file operations beyond writing, such as changing
324- /// file permissions:
329+ /// # Example
325330 ///
326331 /// ```rust
327332 /// # use std::io;
328333 /// # use std::io::Write;
329334 /// # use cap_tempfile::cap_std;
335+ /// # use cap_std::fs::PermissionsExt;
330336 /// # fn main() -> io::Result<()> {
331337 /// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
332- /// # let somedir = cap_std::fs_utf8::Dir::from_cap_std((&*somedir).try_clone()?);
333338 /// use cap_std_ext::prelude::*;
334339 /// let contents = b"hello world\n";
335- /// somedir.atomic_replace_with("somefilename", |f| -> io::Result<_> {
340+ /// let perms = cap_std::fs::Permissions::from_mode(0o600);
341+ /// somedir.atomic_replace_with("somefilename", Some(perms), |f| -> io::Result<_> {
336342 /// 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)?;
341343 /// Ok(())
342344 /// })
343345 /// # }
@@ -347,6 +349,7 @@ pub trait CapStdExtDirExtUtf8 {
347349 fn atomic_replace_with < F , T , E > (
348350 & self ,
349351 destname : impl AsRef < Utf8Path > ,
352+ perms : Option < cap_std:: fs:: Permissions > ,
350353 f : F ,
351354 ) -> std:: result:: Result < T , E >
352355 where
@@ -710,6 +713,7 @@ impl CapStdExtDirExt for Dir {
710713 fn atomic_replace_with < F , T , E > (
711714 & self ,
712715 destname : impl AsRef < Path > ,
716+ perms : Option < cap_std:: fs:: Permissions > ,
713717 f : F ,
714718 ) -> std:: result:: Result < T , E >
715719 where
@@ -718,23 +722,45 @@ impl CapStdExtDirExt for Dir {
718722 {
719723 let destname = destname. as_ref ( ) ;
720724 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 ( ) ) ;
725+
727726 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) ?;
727+ #[ cfg( unix) ]
728+ let default_perms = t. as_file ( ) . metadata ( ) ?. permissions ( ) ;
729+ // cap_tempfile doesn't support passing permissions on creation yet.
730+ // https://github.com/bytecodealliance/cap-std/pull/390
731+ #[ cfg( unix) ]
732+ {
733+ let zeroperms = cap_std:: fs:: PermissionsExt :: from_mode ( 0o000 ) ;
734+ t. as_file ( ) . set_permissions ( zeroperms) ?;
731735 }
736+
732737 // We always operate in terms of buffered writes
733738 let mut bufw = std:: io:: BufWriter :: new ( t) ;
734739 // Call the provided closure to generate the file content
735740 let r = f ( & mut bufw) ?;
736741 // Flush the buffer, get the TempFile
737742 t = bufw. into_inner ( ) . map_err ( From :: from) ?;
743+ // Set the file permissions
744+ // This must be done after flushing the application buffer else Linux clears the security related bits:
745+ // https://github.com/torvalds/linux/blob/94305e83eccb3120c921cd3a015cd74731140bac/fs/inode.c#L2360
746+ if let Some ( perms) = perms {
747+ t. as_file ( ) . set_permissions ( perms) ?;
748+ } else {
749+ let existing_metadata = d. symlink_metadata_optional ( destname) ?;
750+ // If the target is already a file, then acquire its mode, which we will preserve by default.
751+ // We don't follow symlinks here for replacement, and so we definitely don't want to pick up its mode.
752+ let existing_perms = existing_metadata
753+ . filter ( |m| m. is_file ( ) )
754+ . map ( |m| m. permissions ( ) ) ;
755+ if let Some ( existing_perms) = existing_perms {
756+ // Apply the existing permissions, if we have them
757+ t. as_file ( ) . set_permissions ( existing_perms) ?;
758+ } else {
759+ // Else restore default permissions
760+ #[ cfg( unix) ]
761+ t. as_file ( ) . set_permissions ( default_perms) ?;
762+ }
763+ }
738764 // fsync the TempFile
739765 t. as_file ( ) . sync_all ( ) ?;
740766 // rename the TempFile
@@ -745,7 +771,7 @@ impl CapStdExtDirExt for Dir {
745771 }
746772
747773 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 ( ) ) )
774+ self . atomic_replace_with ( destname, None , |f| f. write_all ( contents. as_ref ( ) ) )
749775 }
750776
751777 fn atomic_write_with_perms (
@@ -754,19 +780,8 @@ impl CapStdExtDirExt for Dir {
754780 contents : impl AsRef < [ u8 ] > ,
755781 perms : cap_std:: fs:: Permissions ,
756782 ) -> 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- }
783+ self . atomic_replace_with ( destname, Some ( perms) , |f| -> io:: Result < _ > {
767784 f. write_all ( contents. as_ref ( ) ) ?;
768- f. flush ( ) ?;
769- f. get_mut ( ) . as_file_mut ( ) . set_permissions ( perms) ?;
770785 Ok ( ( ) )
771786 } )
772787 }
0 commit comments