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.
@@ -59,16 +77,36 @@ where
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+ . truncate ( false )
86+ . mode ( 0o600 )
87+ . open ( & dest) ?;
6388 let src_fd = src_file. as_raw_fd ( ) ;
6489 let dst_fd = dst_file. as_raw_fd ( ) ;
6590 let result = unsafe { libc:: ioctl ( dst_fd, libc:: FICLONE , src_fd) } ;
6691 if result == 0 {
6792 return Ok ( ( ) ) ;
6893 }
94+ let err = io:: Error :: last_os_error ( ) ;
6995 match fallback {
70- CloneFallback :: Error => Err ( std:: io:: Error :: last_os_error ( ) ) ,
71- CloneFallback :: FSCopy => std:: fs:: copy ( source, dest) . map ( |_| ( ) ) ,
96+ CloneFallback :: Error => {
97+ // FICLONE fails with EINVAL if the destination is not empty.
98+ // Truncate and retry once before giving up.
99+ if err. raw_os_error ( ) == Some ( libc:: EINVAL ) {
100+ dst_file. set_len ( 0 ) ?;
101+ let retry = unsafe { libc:: ioctl ( dst_fd, libc:: FICLONE , src_fd) } ;
102+ if retry == 0 {
103+ return Ok ( ( ) ) ;
104+ }
105+ return Err ( io:: Error :: last_os_error ( ) ) ;
106+ }
107+ Err ( err)
108+ }
109+ CloneFallback :: FSCopy => copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) ) ,
72110 CloneFallback :: SparseCopy => sparse_copy ( source, dest) ,
73111 CloneFallback :: SparseCopyWithoutHole => sparse_copy_without_hole ( source, dest) ,
74112 }
@@ -125,7 +163,14 @@ where
125163 P : AsRef < Path > ,
126164{
127165 let src_file = File :: open ( source) ?;
128- let dst_file = File :: create ( dest) ?;
166+ // Create destination with restrictive permissions initially (to prevent race conditions)
167+ // Mode 0o600 means read/write for owner only
168+ let dst_file = OpenOptions :: new ( )
169+ . create ( true )
170+ . write ( true )
171+ . truncate ( true )
172+ . mode ( 0o600 )
173+ . open ( dest) ?;
129174 let dst_fd = dst_file. as_raw_fd ( ) ;
130175
131176 let size = src_file. metadata ( ) ?. size ( ) ;
@@ -175,7 +220,14 @@ where
175220 P : AsRef < Path > ,
176221{
177222 let mut src_file = File :: open ( source) ?;
178- let dst_file = File :: create ( dest) ?;
223+ // Create destination with restrictive permissions initially (to prevent race conditions)
224+ // Mode 0o600 means read/write for owner only
225+ let dst_file = OpenOptions :: new ( )
226+ . create ( true )
227+ . write ( true )
228+ . truncate ( true )
229+ . mode ( 0o600 )
230+ . open ( dest) ?;
179231 let dst_fd = dst_file. as_raw_fd ( ) ;
180232
181233 let size: usize = src_file. metadata ( ) ?. size ( ) . try_into ( ) . unwrap ( ) ;
@@ -237,18 +289,18 @@ where
237289 // TODO Update the code below to respect the case where
238290 // `--preserve=ownership` is not true.
239291 let mut src_file = File :: open ( & source) ?;
240- let mode = 0o622 & !get_umask ( ) ;
292+ // Create with restrictive permissions initially to prevent race conditions
293+ // Mode 0o600 means read/write for owner only
294+ // Use truncate(true) to ensure we overwrite existing content
241295 let mut dst_file = OpenOptions :: new ( )
242296 . create ( true )
243297 . write ( true )
244- . mode ( mode)
298+ . truncate ( true )
299+ . mode ( 0o600 )
245300 . open ( & dest) ?;
246301
247302 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- }
303+ // No need to set_len(0) since we're already using truncate(true)
252304
253305 let num_bytes_copied = buf_copy:: copy_stream ( & mut src_file, & mut dst_file)
254306 . map_err ( |e| std:: io:: Error :: other ( format ! ( "{e}" ) ) ) ?;
@@ -287,7 +339,9 @@ pub(crate) fn copy_on_write(
287339 }
288340
289341 match copy_method {
290- CopyMethod :: FSCopy => std:: fs:: copy ( source, dest) . map ( |_| ( ) ) ,
342+ CopyMethod :: FSCopy => {
343+ copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) )
344+ }
291345 _ => sparse_copy ( source, dest) ,
292346 }
293347 }
@@ -303,7 +357,7 @@ pub(crate) fn copy_on_write(
303357 if let Ok ( debug) = result {
304358 copy_debug = debug;
305359 }
306- std :: fs :: copy ( source, dest) . map ( |_| ( ) )
360+ copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) )
307361 }
308362 }
309363 ( ReflinkMode :: Never , SparseMode :: Auto ) => {
@@ -322,7 +376,7 @@ pub(crate) fn copy_on_write(
322376
323377 match copy_method {
324378 CopyMethod :: SparseCopyWithoutHole => sparse_copy_without_hole ( source, dest) ,
325- _ => std :: fs :: copy ( source, dest) . map ( |_| ( ) ) ,
379+ _ => copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) ) ,
326380 }
327381 }
328382 }
0 commit comments