rustc/debian/patches/u-Fix-CVE-2022-21658-for-UNIX-like.patch
Fabian Grünbichler 6a74588651 fix cve-2022-21658 std::fs::remove_dir_all
backport to 1.52:
- removed macos/redox impl
- removed ignored test case
- removed no longer needed changes to weak! (which is a v1 macro in
  1.52)
- used RawFd instead of OwnedFd (latter doesn't exist in 1.52)

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-01-21 10:03:40 +01:00

264 lines
8.8 KiB
Diff

From a03c7b755b54e09a3e274974a63c8d0ac32254d7 Mon Sep 17 00:00:00 2001
From: Hans Kratz <hans@appfour.com>
Date: Mon, 17 Jan 2022 09:45:46 +0100
Subject: [PATCH 2/4] Fix CVE-2022-21658 for UNIX-like
~~
backport to 1.52:
- removed macos/redox impl
- removed ignored test case
- removed no longer needed changes to weak! (which is a v1 macro in
1.52)
- used RawFd instead of OwnedFd (latter doesn't exist in 1.52)
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
library/std/src/fs/tests.rs | 70 ++++++++
library/std/src/sys/unix/fs.rs | 279 +++++++++++++++++++++++++++++--
3 files changed, 342 insertions(+), 12 deletions(-)
diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs
index 9a8f1e44f1f..4d1959258be 100644
--- a/library/std/src/fs/tests.rs
+++ b/library/std/src/fs/tests.rs
@@ -4,8 +4,10 @@
use crate::io::{ErrorKind, SeekFrom};
use crate::path::Path;
use crate::str;
+use crate::sync::Arc;
use crate::sys_common::io::test::{tmpdir, TempDir};
use crate::thread;
+use crate::time::{Duration, Instant};
use rand::{rngs::StdRng, RngCore, SeedableRng};
@@ -588,6 +590,21 @@ fn recursive_rmdir_of_symlink() {
}
#[test]
+fn recursive_rmdir_of_file_fails() {
+ // test we do not delete a directly specified file.
+ let tmpdir = tmpdir();
+ let canary = tmpdir.join("do_not_delete");
+ check!(check!(File::create(&canary)).write(b"foo"));
+ let result = fs::remove_dir_all(&canary);
+ #[cfg(unix)]
+ error!(result, "Not a directory");
+ #[cfg(windows)]
+ error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid.
+ assert!(result.is_err());
+ assert!(canary.exists());
+}
+
+#[test]
// only Windows makes a distinction between file and directory symlinks.
#[cfg(windows)]
fn recursive_rmdir_of_file_symlink() {
#[test]
fn unicode_path_is_dir() {
assert!(Path::new(".").is_dir());
diff --git a/library/std/src/sys/unix/fs.rs b/library/std/src/sys/unix/fs.rs
index a4fff9b2e64..a150d1a9aac 100644
--- a/library/std/src/sys/unix/fs.rs
+++ b/library/std/src/sys/unix/fs.rs
@@ -48,8 +48,6 @@ use libc::{
dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, readdir64_r, stat64,
};
-pub use crate::sys_common::fs::remove_dir_all;
-
pub struct File(FileDesc);
// FIXME: This should be available on Linux with all `target_env`.
@@ -212,7 +210,7 @@ pub struct DirEntry {
target_os = "fuchsia",
target_os = "redox"
))]
- name: Box<[u8]>,
+ name: CString,
}
#[derive(Clone, Debug)]
@@ -439,8 +437,6 @@ impl Iterator for ReadDir {
target_os = "illumos"
))]
fn next(&mut self) -> Option<io::Result<DirEntry>> {
- use crate::slice;
-
unsafe {
loop {
// Although readdir_r(3) would be a correct function to use here because
@@ -458,14 +454,10 @@ impl Iterator for ReadDir {
};
}
- let name = (*entry_ptr).d_name.as_ptr();
- let namelen = libc::strlen(name) as usize;
-
let ret = DirEntry {
entry: *entry_ptr,
- name: slice::from_raw_parts(name as *const u8, namelen as usize)
- .to_owned()
- .into_boxed_slice(),
+ // d_name is guaranteed to be null-terminated.
+ name: CStr::from_ptr((*entry_ptr).d_name.as_ptr()).to_owned(),
dir: Arc::clone(&self.inner),
};
if ret.name_bytes() != b"." && ret.name_bytes() != b".." {
@@ -645,7 +637,26 @@ impl DirEntry {
target_os = "redox"
))]
fn name_bytes(&self) -> &[u8] {
- &*self.name
+ self.name.as_bytes()
+ }
+
+ #[cfg(not(any(
+ target_os = "solaris",
+ target_os = "illumos",
+ target_os = "fuchsia",
+ target_os = "redox"
+ )))]
+ fn name_cstr(&self) -> &CStr {
+ unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) }
+ }
+ #[cfg(any(
+ target_os = "solaris",
+ target_os = "illumos",
+ target_os = "fuchsia",
+ target_os = "redox"
+ ))]
+ fn name_cstr(&self) -> &CStr {
+ &self.name
}
}
@@ -1330,3 +1341,123 @@ pub fn copy(from: &Path, to: &Path) -> i
})?;
Ok(bytes_copied as u64)
}
+
+pub use remove_dir_impl::remove_dir_all;
+
+// Modern implementation using openat(), unlinkat() and fdopendir()
+#[cfg(not(any(all(target_os = "macos", target_arch = "x86_64"), target_os = "redox")))]
+mod remove_dir_impl {
+ use super::{cstr, lstat, Dir, InnerReadDir, ReadDir};
+ use crate::ffi::CStr;
+ use crate::io;
+ use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
+ use crate::os::unix::prelude::RawFd;
+ use crate::path::{Path, PathBuf};
+ use crate::sync::Arc;
+ use crate::sys::{cvt, cvt_r};
+ use libc::{fdopendir, openat, unlinkat};
+
+ pub fn openat_nofollow_dironly(parent_fd: Option<RawFd>, p: &CStr) -> io::Result<RawFd> {
+ let fd = cvt_r(|| unsafe {
+ openat(
+ parent_fd.unwrap_or(libc::AT_FDCWD),
+ p.as_ptr(),
+ libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY,
+ )
+ })?;
+ Ok(fd)
+ }
+
+ fn fdreaddir(dir_fd: RawFd) -> io::Result<(ReadDir, RawFd)> {
+ let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) };
+ if ptr.is_null() {
+ return Err(io::Error::last_os_error());
+ }
+ let dirp = Dir(ptr);
+ // file descriptor is automatically closed by libc::closedir() now, so give up ownership
+ let new_parent_fd = dir_fd.into_raw_fd();
+ // a valid root is not needed because we do not call any functions involving the full path
+ // of the DirEntrys.
+ let dummy_root = PathBuf::new();
+ Ok((
+ ReadDir {
+ inner: Arc::new(InnerReadDir { dirp, root: dummy_root }),
+ #[cfg(not(any(
+ target_os = "solaris",
+ target_os = "illumos",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ end_of_stream: false,
+ },
+ new_parent_fd,
+ ))
+ }
+
+ fn remove_dir_all_recursive(parent_fd: Option<RawFd>, p: &Path) -> io::Result<()> {
+ let pcstr = cstr(p)?;
+
+ // entry is expected to be a directory, open as such
+ let fd = openat_nofollow_dironly(parent_fd, &pcstr)?;
+
+ // open the directory passing ownership of the fd
+ let (dir, fd) = fdreaddir(fd)?;
+ for child in dir {
+ let child = child?;
+ let child_is_dir = if cfg!(any(
+ target_os = "solaris",
+ target_os = "illumos",
+ target_os = "haiku",
+ target_os = "vxworks"
+ )) {
+ // no d_type in dirent
+ None
+ } else {
+ match child.entry.d_type {
+ libc::DT_UNKNOWN => None,
+ libc::DT_DIR => Some(true),
+ _ => Some(false),
+ }
+ };
+ match child_is_dir {
+ Some(true) => {
+ remove_dir_all_recursive(Some(fd), Path::new(&child.file_name()))?;
+ }
+ Some(false) => {
+ cvt(unsafe { unlinkat(fd, child.name_cstr().as_ptr(), 0) })?;
+ }
+ None => match cvt(unsafe { unlinkat(fd, child.name_cstr().as_ptr(), 0) }) {
+ // type unknown - try to unlink
+ Err(err)
+ if err.raw_os_error() == Some(libc::EISDIR)
+ || err.raw_os_error() == Some(libc::EPERM) =>
+ {
+ // if the file is a directory unlink fails with EISDIR on Linux and EPERM everyhwere else
+ remove_dir_all_recursive(Some(fd), Path::new(&child.file_name()))?;
+ }
+ result => {
+ result?;
+ }
+ },
+ }
+ }
+
+ // unlink the directory after removing its contents
+ cvt(unsafe {
+ unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), pcstr.as_ptr(), libc::AT_REMOVEDIR)
+ })?;
+ Ok(())
+ }
+
+ pub fn remove_dir_all(p: &Path) -> io::Result<()> {
+ // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
+ // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
+ // into symlinks.
+ let attr = lstat(p)?;
+ if attr.file_type().is_symlink() {
+ crate::fs::remove_file(p)
+ } else {
+ remove_dir_all_recursive(None, p)
+ }
+ }
+}
--
2.32.0