Skip to content

Commit

Permalink
Rollup merge of rust-lang#58803 - haraldh:fs_copy_fix, r=alexcrichton
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Centril committed Mar 10, 2019
2 parents 020d490 + fb98ca7 commit 1dae14b
Showing 1 changed file with 29 additions and 12 deletions.
41 changes: 29 additions & 12 deletions src/libstd/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,8 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
use crate::cmp;
use crate::fs::File;
use crate::fs::{File, OpenOptions};
use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use crate::sync::atomic::{AtomicBool, Ordering};

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

if !from.is_file() {
return Err(Error::new(ErrorKind::InvalidInput,
"the source path is not an existing regular file"))
}

let mut reader = File::open(from)?;
let mut writer = File::create(to)?;

let (perm, len) = {
let metadata = reader.metadata()?;
(metadata.permissions(), metadata.size())
if !metadata.is_file() {
return Err(Error::new(
ErrorKind::InvalidInput,
"the source path is not an existing regular file",
));
}
(metadata.permissions(), metadata.len())
};

let mut writer = OpenOptions::new()
// create the file with the correct mode right away
.mode(perm.mode())
.write(true)
.create(true)
.truncate(true)
.open(to)?;

let writer_metadata = writer.metadata()?;
if writer_metadata.is_file() {
// Set the correct file permissions, in case the file already existed.
// Don't set the permissions on already existing non-files like
// pipes/FIFOs or device nodes.
writer.set_permissions(perm)?;
}

let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
let mut written = 0u64;
while written < len {
Expand Down Expand Up @@ -919,21 +937,20 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
match err.raw_os_error() {
Some(os_err) if os_err == libc::ENOSYS
|| os_err == libc::EXDEV
|| os_err == libc::EINVAL
|| os_err == libc::EPERM => {
// Try fallback io::copy if either:
// - Kernel version is < 4.5 (ENOSYS)
// - Files are mounted on different fs (EXDEV)
// - copy_file_range is disallowed, for example by seccomp (EPERM)
// - copy_file_range cannot be used with pipes or device nodes (EINVAL)
assert_eq!(written, 0);
let ret = io::copy(&mut reader, &mut writer)?;
writer.set_permissions(perm)?;
return Ok(ret)
return io::copy(&mut reader, &mut writer);
},
_ => return Err(err),
}
}
}
}
writer.set_permissions(perm)?;
Ok(written)
}

0 comments on commit 1dae14b

Please sign in to comment.