]> git.proxmox.com Git - rustc.git/commitdiff
fix cve-2022-21658 std::fs::remove_dir_all
authorFabian Grünbichler <f.gruenbichler@proxmox.com>
Thu, 20 Jan 2022 13:57:58 +0000 (14:57 +0100)
committerFabian Grünbichler <f.gruenbichler@proxmox.com>
Fri, 21 Jan 2022 09:03:40 +0000 (10:03 +0100)
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>
debian/patches/series
debian/patches/u-Fix-CVE-2022-21658-for-UNIX-like.patch [new file with mode: 0644]

index b84f62f8447b6526ecc5a918d10c3864fe3b0f28..6214de6f137a6ceea1d09d2e6a1727cf9c8e23ae 100644 (file)
@@ -50,3 +50,4 @@ d-fix-mips64el-bootstrap.patch
 
 # Work around for some porterboxes, keep this commented
 #d-host-duplicates.patch
+u-Fix-CVE-2022-21658-for-UNIX-like.patch
diff --git a/debian/patches/u-Fix-CVE-2022-21658-for-UNIX-like.patch b/debian/patches/u-Fix-CVE-2022-21658-for-UNIX-like.patch
new file mode 100644 (file)
index 0000000..a47747c
--- /dev/null
@@ -0,0 +1,263 @@
+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
+