From f8f4c4052715994338757244fcaacc093f3c6808 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 29 Jan 2022 11:23:28 -0500 Subject: [PATCH 1/2] fs: Don't copy d_name from struct dirent The dirent returned from readdir() is only guaranteed to be valid for d_reclen bytes on common platforms. Since we copy the name separately anyway, we can copy everything except d_name into DirEntry::entry. Fixes #93384. --- library/std/src/sys/unix/fs.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/unix/fs.rs b/library/std/src/sys/unix/fs.rs index 5b2199c2b7fa4..65e000d9215a5 100644 --- a/library/std/src/sys/unix/fs.rs +++ b/library/std/src/sys/unix/fs.rs @@ -489,10 +489,18 @@ impl Iterator for ReadDir { }; } + // Only d_reclen bytes of *entry_ptr are valid, so we can't just copy the + // whole thing (#93384). Instead, copy everything except the name. + let entry_bytes = entry_ptr as *const u8; + let entry_name = ptr::addr_of!((*entry_ptr).d_name) as *const u8; + let name_offset = entry_name.offset_from(entry_bytes) as usize; + let mut entry: dirent64 = mem::zeroed(); + ptr::copy_nonoverlapping(entry_bytes, &mut entry as *mut _ as *mut u8, name_offset); + let ret = DirEntry { - entry: *entry_ptr, + entry, // d_name is guaranteed to be null-terminated. - name: CStr::from_ptr((*entry_ptr).d_name.as_ptr()).to_owned(), + name: CStr::from_ptr(entry_name as *const _).to_owned(), dir: Arc::clone(&self.inner), }; if ret.name_bytes() != b"." && ret.name_bytes() != b".." { From d0c8b29ec66e1ddf9a4f722969794f8ebc6f07e7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 29 Jan 2022 12:18:27 -0500 Subject: [PATCH 2/2] fs: Add a regression test for #93384 --- library/std/src/fs/tests.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index a62c01ef29b27..16b8bf68242ef 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1504,3 +1504,19 @@ fn create_dir_long_paths() { let path = Path::new(""); assert_eq!(path.canonicalize().unwrap_err().kind(), crate::io::ErrorKind::NotFound); } + +/// Ensure ReadDir works on large directories. +/// Regression test for https://github.com/rust-lang/rust/issues/93384. +#[test] +fn read_large_dir() { + let tmpdir = tmpdir(); + + let count = 32 * 1024; + for i in 0..count { + check!(fs::File::create(tmpdir.join(&i.to_string()))); + } + + for entry in fs::read_dir(tmpdir.path()).unwrap() { + entry.unwrap(); + } +}