Skip to content

Commit 5cf5832

Browse files
committed
fs::copy() 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
1 parent 1a19c46 commit 5cf5832

File tree

1 file changed

+35
-39
lines changed

1 file changed

+35
-39
lines changed

src/libstd/sys/unix/fs.rs

+35-39
Original file line numberDiff line numberDiff line change
@@ -827,13 +827,14 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
827827
Ok(PathBuf::from(OsString::from_vec(buf)))
828828
}
829829

830-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
831-
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
830+
fn open_and_set_permissions(
831+
from: &Path,
832+
to: &Path,
833+
) -> io::Result<(crate::fs::File, crate::fs::File, u64)> {
832834
use crate::fs::{File, OpenOptions};
833835
use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
834836

835-
let mut reader = File::open(from)?;
836-
837+
let reader = File::open(from)?;
837838
let (perm, len) = {
838839
let metadata = reader.metadata()?;
839840
if !metadata.is_file() {
@@ -844,30 +845,33 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
844845
}
845846
(metadata.permissions(), metadata.len())
846847
};
847-
848-
let mut writer = OpenOptions::new()
848+
let writer = OpenOptions::new()
849849
// create the file with the correct mode right away
850850
.mode(perm.mode())
851851
.write(true)
852852
.create(true)
853853
.truncate(true)
854854
.open(to)?;
855-
856855
let writer_metadata = writer.metadata()?;
857856
if writer_metadata.is_file() {
858857
// Set the correct file permissions, in case the file already existed.
859858
// Don't set the permissions on already existing non-files like
860859
// pipes/FIFOs or device nodes.
861860
writer.set_permissions(perm)?;
862861
}
862+
Ok((reader, writer, len))
863+
}
864+
865+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
866+
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
867+
let (mut reader, mut writer, _) = open_and_set_permissions(from, to)?;
863868

864869
io::copy(&mut reader, &mut writer)
865870
}
866871

867872
#[cfg(any(target_os = "linux", target_os = "android"))]
868873
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
869874
use crate::cmp;
870-
use crate::fs::File;
871875
use crate::sync::atomic::{AtomicBool, Ordering};
872876

873877
// Kernel prior to 4.5 don't have copy_file_range
@@ -893,17 +897,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
893897
)
894898
}
895899

896-
if !from.is_file() {
897-
return Err(Error::new(ErrorKind::InvalidInput,
898-
"the source path is not an existing regular file"))
899-
}
900-
901-
let mut reader = File::open(from)?;
902-
let mut writer = File::create(to)?;
903-
let (perm, len) = {
904-
let metadata = reader.metadata()?;
905-
(metadata.permissions(), metadata.size())
906-
};
900+
let (mut reader, mut writer, len) = open_and_set_permissions(from, to)?;
907901

908902
let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
909903
let mut written = 0u64;
@@ -913,13 +907,14 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
913907
let copy_result = unsafe {
914908
// We actually don't have to adjust the offsets,
915909
// because copy_file_range adjusts the file offset automatically
916-
cvt(copy_file_range(reader.as_raw_fd(),
917-
ptr::null_mut(),
918-
writer.as_raw_fd(),
919-
ptr::null_mut(),
920-
bytes_to_copy,
921-
0)
922-
)
910+
cvt(copy_file_range(
911+
reader.as_raw_fd(),
912+
ptr::null_mut(),
913+
writer.as_raw_fd(),
914+
ptr::null_mut(),
915+
bytes_to_copy,
916+
0,
917+
))
923918
};
924919
if let Err(ref copy_err) = copy_result {
925920
match copy_err.raw_os_error() {
@@ -937,23 +932,24 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
937932
Ok(ret) => written += ret as u64,
938933
Err(err) => {
939934
match err.raw_os_error() {
940-
Some(os_err) if os_err == libc::ENOSYS
941-
|| os_err == libc::EXDEV
942-
|| os_err == libc::EPERM => {
943-
// Try fallback io::copy if either:
944-
// - Kernel version is < 4.5 (ENOSYS)
945-
// - Files are mounted on different fs (EXDEV)
946-
// - copy_file_range is disallowed, for example by seccomp (EPERM)
947-
assert_eq!(written, 0);
948-
let ret = io::copy(&mut reader, &mut writer)?;
949-
writer.set_permissions(perm)?;
950-
return Ok(ret)
951-
},
935+
Some(os_err)
936+
if os_err == libc::ENOSYS
937+
|| os_err == libc::EXDEV
938+
|| os_err == libc::EINVAL
939+
|| os_err == libc::EPERM =>
940+
{
941+
// Try fallback io::copy if either:
942+
// - Kernel version is < 4.5 (ENOSYS)
943+
// - Files are mounted on different fs (EXDEV)
944+
// - copy_file_range is disallowed, for example by seccomp (EPERM)
945+
// - copy_file_range cannot be used with pipes or device nodes (EINVAL)
946+
assert_eq!(written, 0);
947+
return io::copy(&mut reader, &mut writer);
948+
}
952949
_ => return Err(err),
953950
}
954951
}
955952
}
956953
}
957-
writer.set_permissions(perm)?;
958954
Ok(written)
959955
}

0 commit comments

Comments
 (0)