Skip to content

Commit af75314

Browse files
committed
Fix possibly endless loop in ReadDir iterator
Certain directories in `/proc` can cause the `ReadDir` iterator to loop indefinitely. We get an error code (22) when calling libc's `readdir_r` on these directories, but `entry_ptr` is `NULL` at the same time, signalling the end of the directory stream. This change introduces an internal state to the iterator such that the `Some(Err(..))` value will only be returned once when calling `next`. Subsequent calls will return `None`. fixes #50619
1 parent ef8cb40 commit af75314

File tree

1 file changed

+24
-7
lines changed

1 file changed

+24
-7
lines changed

src/libstd/sys/unix/fs.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ struct InnerReadDir {
5757
}
5858

5959
#[derive(Clone)]
60-
pub struct ReadDir(Arc<InnerReadDir>);
60+
pub struct ReadDir {
61+
inner: Arc<InnerReadDir>,
62+
end_of_stream: bool,
63+
}
6164

6265
struct Dir(*mut libc::DIR);
6366

@@ -213,7 +216,7 @@ impl fmt::Debug for ReadDir {
213216
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214217
// This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
215218
// Thus the result will be e g 'ReadDir("/home")'
216-
fmt::Debug::fmt(&*self.0.root, f)
219+
fmt::Debug::fmt(&*self.inner.root, f)
217220
}
218221
}
219222

@@ -229,7 +232,7 @@ impl Iterator for ReadDir {
229232
// is safe to use in threaded applications and it is generally preferred
230233
// over the readdir_r(3C) function.
231234
super::os::set_errno(0);
232-
let entry_ptr = libc::readdir(self.0.dirp.0);
235+
let entry_ptr = libc::readdir(self.inner.dirp.0);
233236
if entry_ptr.is_null() {
234237
// NULL can mean either the end is reached or an error occurred.
235238
// So we had to clear errno beforehand to check for an error now.
@@ -257,14 +260,25 @@ impl Iterator for ReadDir {
257260

258261
#[cfg(not(any(target_os = "solaris", target_os = "fuchsia")))]
259262
fn next(&mut self) -> Option<io::Result<DirEntry>> {
263+
if self.end_of_stream {
264+
return None;
265+
}
266+
260267
unsafe {
261268
let mut ret = DirEntry {
262269
entry: mem::zeroed(),
263270
dir: self.clone(),
264271
};
265272
let mut entry_ptr = ptr::null_mut();
266273
loop {
267-
if readdir64_r(self.0.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 {
274+
if readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 {
275+
if entry_ptr.is_null() {
276+
// We encountered an error (which will be returned in this iteration), but
277+
// we also reached the end of the directory stream. The `end_of_stream`
278+
// flag is enabled to make sure that we return `None` in the next iteration
279+
// (instead of looping forever)
280+
self.end_of_stream = true;
281+
}
268282
return Some(Err(Error::last_os_error()))
269283
}
270284
if entry_ptr.is_null() {
@@ -287,7 +301,7 @@ impl Drop for Dir {
287301

288302
impl DirEntry {
289303
pub fn path(&self) -> PathBuf {
290-
self.dir.0.root.join(OsStr::from_bytes(self.name_bytes()))
304+
self.dir.inner.root.join(OsStr::from_bytes(self.name_bytes()))
291305
}
292306

293307
pub fn file_name(&self) -> OsString {
@@ -296,7 +310,7 @@ impl DirEntry {
296310

297311
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
298312
pub fn metadata(&self) -> io::Result<FileAttr> {
299-
let fd = cvt(unsafe {dirfd(self.dir.0.dirp.0)})?;
313+
let fd = cvt(unsafe {dirfd(self.dir.inner.dirp.0)})?;
300314
let mut stat: stat64 = unsafe { mem::zeroed() };
301315
cvt(unsafe {
302316
fstatat(fd,
@@ -692,7 +706,10 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
692706
Err(Error::last_os_error())
693707
} else {
694708
let inner = InnerReadDir { dirp: Dir(ptr), root };
695-
Ok(ReadDir(Arc::new(inner)))
709+
Ok(ReadDir{
710+
inner: Arc::new(inner),
711+
end_of_stream: false,
712+
})
696713
}
697714
}
698715
}

0 commit comments

Comments
 (0)