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
This commit is contained in:
sharkdp 2018-06-12 21:03:27 +02:00
parent ef8cb40c9c
commit af75314ecd

View File

@ -57,7 +57,10 @@ struct InnerReadDir {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct ReadDir(Arc<InnerReadDir>); pub struct ReadDir {
inner: Arc<InnerReadDir>,
end_of_stream: bool,
}
struct Dir(*mut libc::DIR); struct Dir(*mut libc::DIR);
@ -213,7 +216,7 @@ impl fmt::Debug for ReadDir {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
// Thus the result will be e g 'ReadDir("/home")' // Thus the result will be e g 'ReadDir("/home")'
fmt::Debug::fmt(&*self.0.root, f) fmt::Debug::fmt(&*self.inner.root, f)
} }
} }
@ -229,7 +232,7 @@ impl Iterator for ReadDir {
// is safe to use in threaded applications and it is generally preferred // is safe to use in threaded applications and it is generally preferred
// over the readdir_r(3C) function. // over the readdir_r(3C) function.
super::os::set_errno(0); super::os::set_errno(0);
let entry_ptr = libc::readdir(self.0.dirp.0); let entry_ptr = libc::readdir(self.inner.dirp.0);
if entry_ptr.is_null() { if entry_ptr.is_null() {
// NULL can mean either the end is reached or an error occurred. // NULL can mean either the end is reached or an error occurred.
// So we had to clear errno beforehand to check for an error now. // So we had to clear errno beforehand to check for an error now.
@ -257,6 +260,10 @@ impl Iterator for ReadDir {
#[cfg(not(any(target_os = "solaris", target_os = "fuchsia")))] #[cfg(not(any(target_os = "solaris", target_os = "fuchsia")))]
fn next(&mut self) -> Option<io::Result<DirEntry>> { fn next(&mut self) -> Option<io::Result<DirEntry>> {
if self.end_of_stream {
return None;
}
unsafe { unsafe {
let mut ret = DirEntry { let mut ret = DirEntry {
entry: mem::zeroed(), entry: mem::zeroed(),
@ -264,7 +271,14 @@ impl Iterator for ReadDir {
}; };
let mut entry_ptr = ptr::null_mut(); let mut entry_ptr = ptr::null_mut();
loop { loop {
if readdir64_r(self.0.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 { if readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 {
if entry_ptr.is_null() {
// We encountered an error (which will be returned in this iteration), but
// we also reached the end of the directory stream. The `end_of_stream`
// flag is enabled to make sure that we return `None` in the next iteration
// (instead of looping forever)
self.end_of_stream = true;
}
return Some(Err(Error::last_os_error())) return Some(Err(Error::last_os_error()))
} }
if entry_ptr.is_null() { if entry_ptr.is_null() {
@ -287,7 +301,7 @@ impl Drop for Dir {
impl DirEntry { impl DirEntry {
pub fn path(&self) -> PathBuf { pub fn path(&self) -> PathBuf {
self.dir.0.root.join(OsStr::from_bytes(self.name_bytes())) self.dir.inner.root.join(OsStr::from_bytes(self.name_bytes()))
} }
pub fn file_name(&self) -> OsString { pub fn file_name(&self) -> OsString {
@ -296,7 +310,7 @@ impl DirEntry {
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
pub fn metadata(&self) -> io::Result<FileAttr> { pub fn metadata(&self) -> io::Result<FileAttr> {
let fd = cvt(unsafe {dirfd(self.dir.0.dirp.0)})?; let fd = cvt(unsafe {dirfd(self.dir.inner.dirp.0)})?;
let mut stat: stat64 = unsafe { mem::zeroed() }; let mut stat: stat64 = unsafe { mem::zeroed() };
cvt(unsafe { cvt(unsafe {
fstatat(fd, fstatat(fd,
@ -692,7 +706,10 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
Err(Error::last_os_error()) Err(Error::last_os_error())
} else { } else {
let inner = InnerReadDir { dirp: Dir(ptr), root }; let inner = InnerReadDir { dirp: Dir(ptr), root };
Ok(ReadDir(Arc::new(inner))) Ok(ReadDir{
inner: Arc::new(inner),
end_of_stream: false,
})
} }
} }
} }