Skip to content

Commit ad8e2b1

Browse files
authored
Rollup merge of rust-lang#58803 - haraldh:fs_copy_fix, r=alexcrichton
fs::copy() unix: 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 70d1150 + 5cf5832 commit ad8e2b1

File tree

1 file changed

+60
-44
lines changed

1 file changed

+60
-44
lines changed

src/libstd/sys/unix/fs.rs

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

830+
fn open_and_set_permissions(
831+
from: &Path,
832+
to: &Path,
833+
) -> io::Result<(crate::fs::File, crate::fs::File, u64)> {
834+
use crate::fs::{File, OpenOptions};
835+
use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
836+
837+
let reader = File::open(from)?;
838+
let (perm, len) = {
839+
let metadata = reader.metadata()?;
840+
if !metadata.is_file() {
841+
return Err(Error::new(
842+
ErrorKind::InvalidInput,
843+
"the source path is not an existing regular file",
844+
));
845+
}
846+
(metadata.permissions(), metadata.len())
847+
};
848+
let writer = OpenOptions::new()
849+
// create the file with the correct mode right away
850+
.mode(perm.mode())
851+
.write(true)
852+
.create(true)
853+
.truncate(true)
854+
.open(to)?;
855+
let writer_metadata = writer.metadata()?;
856+
if writer_metadata.is_file() {
857+
// Set the correct file permissions, in case the file already existed.
858+
// Don't set the permissions on already existing non-files like
859+
// pipes/FIFOs or device nodes.
860+
writer.set_permissions(perm)?;
861+
}
862+
Ok((reader, writer, len))
863+
}
864+
830865
#[cfg(not(any(target_os = "linux", target_os = "android")))]
831866
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
832-
use crate::fs::File;
833-
if !from.is_file() {
834-
return Err(Error::new(ErrorKind::InvalidInput,
835-
"the source path is not an existing regular file"))
836-
}
867+
let (mut reader, mut writer, _) = open_and_set_permissions(from, to)?;
837868

838-
let mut reader = File::open(from)?;
839-
let mut writer = File::create(to)?;
840-
let perm = reader.metadata()?.permissions();
841-
842-
let ret = io::copy(&mut reader, &mut writer)?;
843-
writer.set_permissions(perm)?;
844-
Ok(ret)
869+
io::copy(&mut reader, &mut writer)
845870
}
846871

847872
#[cfg(any(target_os = "linux", target_os = "android"))]
848873
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
849874
use crate::cmp;
850-
use crate::fs::File;
851875
use crate::sync::atomic::{AtomicBool, Ordering};
852876

853877
// Kernel prior to 4.5 don't have copy_file_range
@@ -873,17 +897,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
873897
)
874898
}
875899

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

888902
let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
889903
let mut written = 0u64;
@@ -893,13 +907,14 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
893907
let copy_result = unsafe {
894908
// We actually don't have to adjust the offsets,
895909
// because copy_file_range adjusts the file offset automatically
896-
cvt(copy_file_range(reader.as_raw_fd(),
897-
ptr::null_mut(),
898-
writer.as_raw_fd(),
899-
ptr::null_mut(),
900-
bytes_to_copy,
901-
0)
902-
)
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+
))
903918
};
904919
if let Err(ref copy_err) = copy_result {
905920
match copy_err.raw_os_error() {
@@ -917,23 +932,24 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
917932
Ok(ret) => written += ret as u64,
918933
Err(err) => {
919934
match err.raw_os_error() {
920-
Some(os_err) if os_err == libc::ENOSYS
921-
|| os_err == libc::EXDEV
922-
|| os_err == libc::EPERM => {
923-
// Try fallback io::copy if either:
924-
// - Kernel version is < 4.5 (ENOSYS)
925-
// - Files are mounted on different fs (EXDEV)
926-
// - copy_file_range is disallowed, for example by seccomp (EPERM)
927-
assert_eq!(written, 0);
928-
let ret = io::copy(&mut reader, &mut writer)?;
929-
writer.set_permissions(perm)?;
930-
return Ok(ret)
931-
},
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+
}
932949
_ => return Err(err),
933950
}
934951
}
935952
}
936953
}
937-
writer.set_permissions(perm)?;
938954
Ok(written)
939955
}

0 commit comments

Comments
 (0)