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