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,27 @@ 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
340
/// # let somedir = cap_std::fs_utf8::Dir::from_cap_std((&*somedir).try_clone()?);
333
341
/// use cap_std_ext::prelude::*;
334
342
/// let contents = b"hello world\n";
335
- /// somedir.atomic_replace_with("somefilename", |f| -> io::Result<_> {
343
+ /// let perms = cap_std::fs::Permissions::from_mode(0o600);
344
+ /// somedir.atomic_replace_with("somefilename", Some(perms), |f| -> io::Result<_> {
336
345
/// 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
346
/// Ok(())
342
347
/// })
343
348
/// # }
@@ -347,6 +352,7 @@ pub trait CapStdExtDirExtUtf8 {
347
352
fn atomic_replace_with < F , T , E > (
348
353
& self ,
349
354
destname : impl AsRef < Utf8Path > ,
355
+ perms : Option < cap_std:: fs:: Permissions > ,
350
356
f : F ,
351
357
) -> std:: result:: Result < T , E >
352
358
where
@@ -710,6 +716,7 @@ impl CapStdExtDirExt for Dir {
710
716
fn atomic_replace_with < F , T , E > (
711
717
& self ,
712
718
destname : impl AsRef < Path > ,
719
+ perms : Option < cap_std:: fs:: Permissions > ,
713
720
f : F ,
714
721
) -> std:: result:: Result < T , E >
715
722
where
@@ -718,23 +725,47 @@ impl CapStdExtDirExt for Dir {
718
725
{
719
726
let destname = destname. as_ref ( ) ;
720
727
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 ( ) ) ;
728
+
729
+ // cap_tempfile doesn't support passing permissions on creation.
730
+ // https://github.com/bytecodealliance/cap-std/pull/390
727
731
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) ?;
732
+ #[ cfg( unix) ]
733
+ let t_metadata = t. as_file ( ) . metadata ( ) ?;
734
+ #[ cfg( unix) ]
735
+ if t_metadata. nlink ( ) > 0 {
736
+ let zeroperms = cap_std:: fs:: PermissionsExt :: from_mode ( 0o000 ) ;
737
+ t. as_file ( ) . set_permissions ( zeroperms) ?;
731
738
}
739
+
732
740
// We always operate in terms of buffered writes
733
741
let mut bufw = std:: io:: BufWriter :: new ( t) ;
734
742
// Call the provided closure to generate the file content
735
743
let r = f ( & mut bufw) ?;
736
744
// Flush the buffer, get the TempFile
737
745
t = bufw. into_inner ( ) . map_err ( From :: from) ?;
746
+ // Set the file permissions
747
+ // This must be done after flushing the application buffer else Linux clears the security related bits:
748
+ // https://github.com/torvalds/linux/blob/94305e83eccb3120c921cd3a015cd74731140bac/fs/inode.c#L2360
749
+ if let Some ( perms) = perms {
750
+ t. as_file ( ) . set_permissions ( perms) ?;
751
+ } else {
752
+ let existing_metadata = d. symlink_metadata_optional ( destname) ?;
753
+ // If the target is already a file, then acquire its mode, which we will preserve by default.
754
+ // We don't follow symlinks here for replacement, and so we definitely don't want to pick up its mode.
755
+ let existing_perms = existing_metadata
756
+ . filter ( |m| m. is_file ( ) )
757
+ . map ( |m| m. permissions ( ) ) ;
758
+ if let Some ( existing_perms) = existing_perms {
759
+ // Apply the existing permissions, if we have them
760
+ t. as_file ( ) . set_permissions ( existing_perms) ?;
761
+ } else {
762
+ // Else restore default permissions
763
+ #[ cfg( unix) ]
764
+ if t_metadata. nlink ( ) > 0 {
765
+ t. as_file ( ) . set_permissions ( t_metadata. permissions ( ) ) ?;
766
+ }
767
+ }
768
+ }
738
769
// fsync the TempFile
739
770
t. as_file ( ) . sync_all ( ) ?;
740
771
// rename the TempFile
@@ -745,7 +776,7 @@ impl CapStdExtDirExt for Dir {
745
776
}
746
777
747
778
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 ( ) ) )
779
+ self . atomic_replace_with ( destname, None , |f| f. write_all ( contents. as_ref ( ) ) )
749
780
}
750
781
751
782
fn atomic_write_with_perms (
@@ -754,19 +785,8 @@ impl CapStdExtDirExt for Dir {
754
785
contents : impl AsRef < [ u8 ] > ,
755
786
perms : cap_std:: fs:: Permissions ,
756
787
) -> 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
- }
788
+ self . atomic_replace_with ( destname, Some ( perms) , |f| -> io:: Result < _ > {
767
789
f. write_all ( contents. as_ref ( ) ) ?;
768
- f. flush ( ) ?;
769
- f. get_mut ( ) . as_file_mut ( ) . set_permissions ( perms) ?;
770
790
Ok ( ( ) )
771
791
} )
772
792
}
0 commit comments