8
8
//!
9
9
//! [`cap_std::fs::Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html
10
10
11
- use cap_primitives:: fs:: FileType ;
11
+ use cap_primitives:: fs:: { FileType , PermissionsExt } ;
12
12
use cap_std:: fs:: { Dir , File , Metadata } ;
13
13
use cap_tempfile:: cap_std;
14
14
use cap_tempfile:: cap_std:: fs:: DirEntry ;
@@ -172,25 +172,26 @@ pub trait CapStdExtDirExt {
172
172
/// require a higher level wrapper which queries the existing file and gathers such metadata
173
173
/// before replacement.
174
174
///
175
- /// # Example, including setting permissions
175
+ /// # Security
176
176
///
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
179
182
///
180
183
/// ```rust
181
184
/// # use std::io;
182
185
/// # use std::io::Write;
183
186
/// # use cap_tempfile::cap_std;
187
+ /// # use cap_std::fs::PermissionsExt;
184
188
/// # fn main() -> io::Result<()> {
185
189
/// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
186
190
/// use cap_std_ext::prelude::*;
187
191
/// 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<_> {
189
194
/// 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)?;
194
195
/// Ok(())
195
196
/// })
196
197
/// # }
@@ -200,6 +201,7 @@ pub trait CapStdExtDirExt {
200
201
fn atomic_replace_with < F , T , E > (
201
202
& self ,
202
203
destname : impl AsRef < Path > ,
204
+ perms : Option < cap_std:: fs:: Permissions > ,
203
205
f : F ,
204
206
) -> std:: result:: Result < T , E >
205
207
where
@@ -318,26 +320,26 @@ pub trait CapStdExtDirExtUtf8 {
318
320
/// require a higher level wrapper which queries the existing file and gathers such metadata
319
321
/// before replacement.
320
322
///
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.
322
328
///
323
- /// The closure may also perform other file operations beyond writing, such as changing
324
- /// file permissions:
329
+ /// # Example
325
330
///
326
331
/// ```rust
327
332
/// # use std::io;
328
333
/// # use std::io::Write;
329
334
/// # use cap_tempfile::cap_std;
335
+ /// # use cap_std::fs::PermissionsExt;
330
336
/// # fn main() -> io::Result<()> {
331
337
/// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
332
- /// # let somedir = cap_std::fs_utf8::Dir::from_cap_std((&*somedir).try_clone()?);
333
338
/// use cap_std_ext::prelude::*;
334
339
/// 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<_> {
336
342
/// 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)?;
341
343
/// Ok(())
342
344
/// })
343
345
/// # }
@@ -347,6 +349,7 @@ pub trait CapStdExtDirExtUtf8 {
347
349
fn atomic_replace_with < F , T , E > (
348
350
& self ,
349
351
destname : impl AsRef < Utf8Path > ,
352
+ perms : Option < cap_std:: fs:: Permissions > ,
350
353
f : F ,
351
354
) -> std:: result:: Result < T , E >
352
355
where
@@ -710,6 +713,7 @@ impl CapStdExtDirExt for Dir {
710
713
fn atomic_replace_with < F , T , E > (
711
714
& self ,
712
715
destname : impl AsRef < Path > ,
716
+ perms : Option < cap_std:: fs:: Permissions > ,
713
717
f : F ,
714
718
) -> std:: result:: Result < T , E >
715
719
where
@@ -718,23 +722,45 @@ impl CapStdExtDirExt for Dir {
718
722
{
719
723
let destname = destname. as_ref ( ) ;
720
724
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
+
727
726
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) ?;
731
735
}
736
+
732
737
// We always operate in terms of buffered writes
733
738
let mut bufw = std:: io:: BufWriter :: new ( t) ;
734
739
// Call the provided closure to generate the file content
735
740
let r = f ( & mut bufw) ?;
736
741
// Flush the buffer, get the TempFile
737
742
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
+ }
738
764
// fsync the TempFile
739
765
t. as_file ( ) . sync_all ( ) ?;
740
766
// rename the TempFile
@@ -745,7 +771,7 @@ impl CapStdExtDirExt for Dir {
745
771
}
746
772
747
773
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 ( ) ) )
749
775
}
750
776
751
777
fn atomic_write_with_perms (
@@ -754,19 +780,8 @@ impl CapStdExtDirExt for Dir {
754
780
contents : impl AsRef < [ u8 ] > ,
755
781
perms : cap_std:: fs:: Permissions ,
756
782
) -> 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 < _ > {
767
784
f. write_all ( contents. as_ref ( ) ) ?;
768
- f. flush ( ) ?;
769
- f. get_mut ( ) . as_file_mut ( ) . set_permissions ( perms) ?;
770
785
Ok ( ( ) )
771
786
} )
772
787
}
0 commit comments