Skip to content

Commit 8931a2b

Browse files
authored
Rollup merge of rust-lang#58803 - haraldh:fs_copy_fix, r=alexcrichton
fs::copy() linux: set file mode early A convenience method like fs::copy() should try to prevent pitfalls a normal user doesn't think about. In case of an empty umask, setting the file mode early prevents temporarily world readable or even writeable files, because the default mode is 0o666. In case the target is a named pipe or special device node, setting the file mode can lead to unwanted side effects, like setting permissons on `/dev/stdout` or for root setting permissions on `/dev/null`. copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or a device like "/dev/null", so fallback to io::copy, too. Fixes: rust-lang#26933 Fixed: rust-lang#37885
2 parents 412e7e1 + fb98ca7 commit 8931a2b

File tree

1 file changed

+29
-12
lines changed

1 file changed

+29
-12
lines changed

src/libstd/sys/unix/fs.rs

+29-12
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,8 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
847847
#[cfg(any(target_os = "linux", target_os = "android"))]
848848
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
849849
use crate::cmp;
850-
use crate::fs::File;
850+
use crate::fs::{File, OpenOptions};
851+
use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
851852
use crate::sync::atomic::{AtomicBool, Ordering};
852853

853854
// Kernel prior to 4.5 don't have copy_file_range
@@ -873,18 +874,35 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
873874
)
874875
}
875876

876-
if !from.is_file() {
877-
return Err(Error::new(ErrorKind::InvalidInput,
878-
"the source path is not an existing regular file"))
879-
}
880-
881877
let mut reader = File::open(from)?;
882-
let mut writer = File::create(to)?;
878+
883879
let (perm, len) = {
884880
let metadata = reader.metadata()?;
885-
(metadata.permissions(), metadata.size())
881+
if !metadata.is_file() {
882+
return Err(Error::new(
883+
ErrorKind::InvalidInput,
884+
"the source path is not an existing regular file",
885+
));
886+
}
887+
(metadata.permissions(), metadata.len())
886888
};
887889

890+
let mut writer = OpenOptions::new()
891+
// create the file with the correct mode right away
892+
.mode(perm.mode())
893+
.write(true)
894+
.create(true)
895+
.truncate(true)
896+
.open(to)?;
897+
898+
let writer_metadata = writer.metadata()?;
899+
if writer_metadata.is_file() {
900+
// Set the correct file permissions, in case the file already existed.
901+
// Don't set the permissions on already existing non-files like
902+
// pipes/FIFOs or device nodes.
903+
writer.set_permissions(perm)?;
904+
}
905+
888906
let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
889907
let mut written = 0u64;
890908
while written < len {
@@ -919,21 +937,20 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
919937
match err.raw_os_error() {
920938
Some(os_err) if os_err == libc::ENOSYS
921939
|| os_err == libc::EXDEV
940+
|| os_err == libc::EINVAL
922941
|| os_err == libc::EPERM => {
923942
// Try fallback io::copy if either:
924943
// - Kernel version is < 4.5 (ENOSYS)
925944
// - Files are mounted on different fs (EXDEV)
926945
// - copy_file_range is disallowed, for example by seccomp (EPERM)
946+
// - copy_file_range cannot be used with pipes or device nodes (EINVAL)
927947
assert_eq!(written, 0);
928-
let ret = io::copy(&mut reader, &mut writer)?;
929-
writer.set_permissions(perm)?;
930-
return Ok(ret)
948+
return io::copy(&mut reader, &mut writer);
931949
},
932950
_ => return Err(err),
933951
}
934952
}
935953
}
936954
}
937-
writer.set_permissions(perm)?;
938955
Ok(written)
939956
}

0 commit comments

Comments
 (0)