about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2022-11-20 18:21:47 +0100
committerGitHub <noreply@github.com>2022-11-20 18:21:47 +0100
commit379d3365fd16a9faf90c98a74f5e7d7b5c0ede23 (patch)
tree29f45fdec2a2132f1a75c3d19e45b591a1ddfcea
parentddb12348cab204cc68d6b8c634f77d070ba0cbfe (diff)
parent56888c1e9b4135b511abd2d8e907099003d12281 (diff)
downloadrust-379d3365fd16a9faf90c98a74f5e7d7b5c0ede23.tar.gz
rust-379d3365fd16a9faf90c98a74f5e7d7b5c0ede23.zip
Rollup merge of #104558 - thomcc:unalign-diriter, r=ChrisDenton
Don't assume `FILE_ID_BOTH_DIR_INFO` will be aligned

Fixes #104530. See that issue for info.

r? `@ChrisDenton`
-rw-r--r--library/std/src/sys/windows/fs.rs33
1 files changed, 23 insertions, 10 deletions
diff --git a/library/std/src/sys/windows/fs.rs b/library/std/src/sys/windows/fs.rs
index ade00750c95..37809803828 100644
--- a/library/std/src/sys/windows/fs.rs
+++ b/library/std/src/sys/windows/fs.rs
@@ -1,5 +1,6 @@
 use crate::os::windows::prelude::*;
 
+use crate::borrow::Cow;
 use crate::ffi::OsString;
 use crate::fmt;
 use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
@@ -719,7 +720,7 @@ impl<'a> DirBuffIter<'a> {
     }
 }
 impl<'a> Iterator for DirBuffIter<'a> {
-    type Item = (&'a [u16], bool);
+    type Item = (Cow<'a, [u16]>, bool);
     fn next(&mut self) -> Option<Self::Item> {
         use crate::mem::size_of;
         let buffer = &self.buffer?[self.cursor..];
@@ -734,15 +735,19 @@ impl<'a> Iterator for DirBuffIter<'a> {
         //   `FileNameLength` bytes)
         let (name, is_directory, next_entry) = unsafe {
             let info = buffer.as_ptr().cast::<c::FILE_ID_BOTH_DIR_INFO>();
-            // Guaranteed to be aligned in documentation for
+            // While this is guaranteed to be aligned in documentation for
             // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_id_both_dir_info
-            assert!(info.is_aligned());
-            let next_entry = (*info).NextEntryOffset as usize;
-            let name = crate::slice::from_raw_parts(
+            // it does not seem that reality is so kind, and assuming this
+            // caused crashes in some cases (https://github.com/rust-lang/rust/issues/104530)
+            // presumably, this can be blamed on buggy filesystem drivers, but who knows.
+            let next_entry = ptr::addr_of!((*info).NextEntryOffset).read_unaligned() as usize;
+            let length = ptr::addr_of!((*info).FileNameLength).read_unaligned() as usize;
+            let attrs = ptr::addr_of!((*info).FileAttributes).read_unaligned();
+            let name = from_maybe_unaligned(
                 ptr::addr_of!((*info).FileName).cast::<u16>(),
-                (*info).FileNameLength as usize / size_of::<u16>(),
+                length / size_of::<u16>(),
             );
-            let is_directory = ((*info).FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) != 0;
+            let is_directory = (attrs & c::FILE_ATTRIBUTE_DIRECTORY) != 0;
 
             (name, is_directory, next_entry)
         };
@@ -755,13 +760,21 @@ impl<'a> Iterator for DirBuffIter<'a> {
 
         // Skip `.` and `..` pseudo entries.
         const DOT: u16 = b'.' as u16;
-        match name {
+        match &name[..] {
             [DOT] | [DOT, DOT] => self.next(),
             _ => Some((name, is_directory)),
         }
     }
 }
 
+unsafe fn from_maybe_unaligned<'a>(p: *const u16, len: usize) -> Cow<'a, [u16]> {
+    if p.is_aligned() {
+        Cow::Borrowed(crate::slice::from_raw_parts(p, len))
+    } else {
+        Cow::Owned((0..len).map(|i| p.add(i).read_unaligned()).collect())
+    }
+}
+
 /// Open a link relative to the parent directory, ensure no symlinks are followed.
 fn open_link_no_reparse(parent: &File, name: &[u16], access: u32) -> io::Result<File> {
     // This is implemented using the lower level `NtCreateFile` function as
@@ -1117,13 +1130,13 @@ fn remove_dir_all_iterative(f: &File, delete: fn(&File) -> io::Result<()>) -> io
             if is_directory {
                 let child_dir = open_link_no_reparse(
                     &dir,
-                    name,
+                    &name,
                     c::SYNCHRONIZE | c::DELETE | c::FILE_LIST_DIRECTORY,
                 )?;
                 dirlist.push(child_dir);
             } else {
                 for i in 1..=MAX_RETRIES {
-                    let result = open_link_no_reparse(&dir, name, c::SYNCHRONIZE | c::DELETE);
+                    let result = open_link_no_reparse(&dir, &name, c::SYNCHRONIZE | c::DELETE);
                     match result {
                         Ok(f) => delete(&f)?,
                         // Already deleted, so skip.