66
77use libc:: { SEEK_DATA , SEEK_HOLE } ;
88use std:: fs:: { File , OpenOptions } ;
9- use std:: io:: Read ;
9+ use std:: io:: { self , Read } ;
1010use std:: os:: unix:: fs:: FileExt ;
1111use std:: os:: unix:: fs:: MetadataExt ;
1212use std:: os:: unix:: fs:: { FileTypeExt , OpenOptionsExt } ;
1313use std:: os:: unix:: io:: AsRawFd ;
1414use std:: path:: Path ;
1515use uucore:: buf_copy;
16- use uucore:: mode:: get_umask;
1716use uucore:: translate;
1817
1918use crate :: {
@@ -50,6 +49,25 @@ enum CopyMethod {
5049 SparseCopyWithoutHole ,
5150}
5251
52+ /// Copy file contents with restrictive permissions initially to prevent race conditions.
53+ /// Creates the destination file with mode 0600 & !umask, copies the content,
54+ /// and lets the caller set the final permissions.
55+ fn copy_file_with_secure_permissions < P > ( source : P , dest : P ) -> io:: Result < u64 >
56+ where
57+ P : AsRef < Path > ,
58+ {
59+ let mut src_file = File :: open ( & source) ?;
60+ // Create destination with restrictive permissions initially (to prevent race conditions)
61+ // Mode 0o600 means read/write for owner only
62+ let mut dst_file = OpenOptions :: new ( )
63+ . create ( true )
64+ . write ( true )
65+ . truncate ( true )
66+ . mode ( 0o600 )
67+ . open ( & dest) ?;
68+ io:: copy ( & mut src_file, & mut dst_file)
69+ }
70+
5371/// Use the Linux `ioctl_ficlone` API to do a copy-on-write clone.
5472///
5573/// `fallback` controls what to do if the system call fails.
5977 P : AsRef < Path > ,
6078{
6179 let src_file = File :: open ( & source) ?;
62- let dst_file = File :: create ( & dest) ?;
80+ // Create destination with restrictive permissions initially (to prevent race conditions)
81+ // Mode 0o600 means read/write for owner only
82+ let dst_file = OpenOptions :: new ( )
83+ . create ( true )
84+ . write ( true )
85+ . mode ( 0o600 )
86+ . open ( & dest) ?;
6387 let src_fd = src_file. as_raw_fd ( ) ;
6488 let dst_fd = dst_file. as_raw_fd ( ) ;
6589 let result = unsafe { libc:: ioctl ( dst_fd, libc:: FICLONE , src_fd) } ;
6892 }
6993 match fallback {
7094 CloneFallback :: Error => Err ( std:: io:: Error :: last_os_error ( ) ) ,
71- CloneFallback :: FSCopy => std :: fs :: copy ( source, dest) . map ( |_| ( ) ) ,
95+ CloneFallback :: FSCopy => copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) ) ,
7296 CloneFallback :: SparseCopy => sparse_copy ( source, dest) ,
7397 CloneFallback :: SparseCopyWithoutHole => sparse_copy_without_hole ( source, dest) ,
7498 }
@@ -125,7 +149,13 @@ where
125149 P : AsRef < Path > ,
126150{
127151 let src_file = File :: open ( source) ?;
128- let dst_file = File :: create ( dest) ?;
152+ // Create destination with restrictive permissions initially (to prevent race conditions)
153+ // Mode 0o600 means read/write for owner only
154+ let dst_file = OpenOptions :: new ( )
155+ . create ( true )
156+ . write ( true )
157+ . mode ( 0o600 )
158+ . open ( dest) ?;
129159 let dst_fd = dst_file. as_raw_fd ( ) ;
130160
131161 let size = src_file. metadata ( ) ?. size ( ) ;
@@ -175,7 +205,13 @@ where
175205 P : AsRef < Path > ,
176206{
177207 let mut src_file = File :: open ( source) ?;
178- let dst_file = File :: create ( dest) ?;
208+ // Create destination with restrictive permissions initially (to prevent race conditions)
209+ // Mode 0o600 means read/write for owner only
210+ let dst_file = OpenOptions :: new ( )
211+ . create ( true )
212+ . write ( true )
213+ . mode ( 0o600 )
214+ . open ( dest) ?;
179215 let dst_fd = dst_file. as_raw_fd ( ) ;
180216
181217 let size: usize = src_file. metadata ( ) ?. size ( ) . try_into ( ) . unwrap ( ) ;
@@ -237,18 +273,18 @@ where
237273 // TODO Update the code below to respect the case where
238274 // `--preserve=ownership` is not true.
239275 let mut src_file = File :: open ( & source) ?;
240- let mode = 0o622 & !get_umask ( ) ;
276+ // Create with restrictive permissions initially to prevent race conditions
277+ // Mode 0o600 means read/write for owner only
278+ // Use truncate(true) to ensure we overwrite existing content
241279 let mut dst_file = OpenOptions :: new ( )
242280 . create ( true )
243281 . write ( true )
244- . mode ( mode)
282+ . truncate ( true )
283+ . mode ( 0o600 )
245284 . open ( & dest) ?;
246285
247286 let dest_is_stream = is_stream ( & dst_file. metadata ( ) ?) ;
248- if !dest_is_stream {
249- // `copy_stream` doesn't clear the dest file, if dest is not a stream, we should clear it manually.
250- dst_file. set_len ( 0 ) ?;
251- }
287+ // No need to set_len(0) since we're already using truncate(true)
252288
253289 let num_bytes_copied = buf_copy:: copy_stream ( & mut src_file, & mut dst_file)
254290 . map_err ( |e| std:: io:: Error :: other ( format ! ( "{e}" ) ) ) ?;
@@ -287,7 +323,9 @@ pub(crate) fn copy_on_write(
287323 }
288324
289325 match copy_method {
290- CopyMethod :: FSCopy => std:: fs:: copy ( source, dest) . map ( |_| ( ) ) ,
326+ CopyMethod :: FSCopy => {
327+ copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) )
328+ }
291329 _ => sparse_copy ( source, dest) ,
292330 }
293331 }
@@ -303,7 +341,7 @@ pub(crate) fn copy_on_write(
303341 if let Ok ( debug) = result {
304342 copy_debug = debug;
305343 }
306- std :: fs :: copy ( source, dest) . map ( |_| ( ) )
344+ copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) )
307345 }
308346 }
309347 ( ReflinkMode :: Never , SparseMode :: Auto ) => {
@@ -322,7 +360,7 @@ pub(crate) fn copy_on_write(
322360
323361 match copy_method {
324362 CopyMethod :: SparseCopyWithoutHole => sparse_copy_without_hole ( source, dest) ,
325- _ => std :: fs :: copy ( source, dest) . map ( |_| ( ) ) ,
363+ _ => copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) ) ,
326364 }
327365 }
328366 }
0 commit comments