-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix possibly endless loop in ReadDir iterator #50630
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @sfackler (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
Can we add a test for this? |
src/libstd/sys/unix/fs.rs
Outdated
@@ -51,6 +51,7 @@ pub struct FileAttr { | |||
pub struct ReadDir { | |||
dirp: Dir, | |||
root: Arc<PathBuf>, | |||
end_of_stream: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be better to just wrap Dir
into an Option
(and mark the pointer within Dir
as NonZero
) as that would both be more semantically accurate, more efficient space wise and more future-proof in terms of preventing mistakes.
Adding a test for this would be fairly difficult, as the original reproducer relies on a fairly specific state of the system that would be pretty hard to emulate in way that wouldn’t spuriously fail all the time. |
I have updated the code to use However, I'm not too happy about my code changes in the @ishitatsuyuki Concerning tests: I would love to write a test for this, but I'm really not sure how to. I also share the same view as @nagisa. Note that there is a way to reproduce the bug in the attached ticket (#50619). |
src/libstd/sys/unix/fs.rs
Outdated
} | ||
if ret.name_bytes() != b"." && ret.name_bytes() != b".." { | ||
return Some(Ok(ret)) | ||
if self.dirp.is_none() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we make this check again just below on 259?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, absolutely - I will remove this if
clause.
src/libstd/sys/unix/fs.rs
Outdated
@@ -49,11 +49,11 @@ pub struct FileAttr { | |||
} | |||
|
|||
pub struct ReadDir { | |||
dirp: Dir, | |||
dirp: Option<Dir>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change related to the infinite loop issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Option
is used to store whether we are at the end of the directory stream (Some(..)
) or not (None
).
src/libstd/sys/unix/fs.rs
Outdated
if readdir64_r(dirp.as_mut().unwrap().0.as_mut(), | ||
&mut ret.entry, | ||
&mut entry_ptr) != 0 { | ||
if entry_ptr.is_null() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the actual change being made, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly. If readdir64_r
returns a non-empty exit code and signals the end of the directory stream by writing nullptr
to entry_ptr
, we still want to return the OS error in this iteration but None
on the next iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this change can be made a bit simpler by just using a null pointer instead of the whole option business. Then the diff just becomes a if self.dirp.0.is_null() { return None; }
at the top of Next, and an if entry_ptr.is_null() { self.dirp.0 = ptr::null_mut(); }
in the error case here.
Ping from triage @sfackler! The author pushed some new commits. |
☔ The latest upstream changes (presumably #51050) made this pull request unmergeable. Please resolve the merge conflicts. |
Option ensures that other areas of code properly handle the null case as
well. Both for this PR and the future changes.
…On Fri, Jun 1, 2018, 08:02 Steven Fackler ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/libstd/sys/unix/fs.rs
<#50630 (comment)>:
> + }
+
+ match self.dirp {
+ None => return None,
+ ref mut dirp @ Some(_) => {
+ unsafe {
+ let mut ret = DirEntry {
+ entry: mem::zeroed(),
+ root: self.root.clone()
+ };
+ let mut entry_ptr = ptr::null_mut();
+ loop {
+ if readdir64_r(dirp.as_mut().unwrap().0.as_mut(),
+ &mut ret.entry,
+ &mut entry_ptr) != 0 {
+ if entry_ptr.is_null() {
I think this change can be made a bit simpler by just using a null pointer
instead of the whole option business. Then the diff just becomes a if
self.dirp.0.is_null() { return None; } at the top of Next, and an if
entry_ptr.is_null() { self.dirp.0 = ptr::null_mut(); } in the error case
here.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#50630 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AApc0oad4EwvkWFqqyB_XeHankt2chd8ks5t4MrrgaJpZM4T6fvb>
.
|
Exactly. Also, I'm personally fine with the solution suggested by @sfackler but I'm not sure if we should actively use Let me know whether I should go forward with @sfackler's suggestion or resolve the merge conflicts in the current version. |
Just rebase it, I’ll r+ it. |
I tried and failed. The whole To be honest, I don't really understand why it has been moved into the As it stands, I either need some help to make this work again (my attempt at resolving the conflicts can be found here: https://gist.github.com/sharkdp/fd60040daf25630f67f095d0fa616b82 - it currently does not pass the borrow-checker) or I move forward with @sfackler's suggestion, which should still work. |
Well, I assume that The problem is, of course, that now we have two distinct, and colliding requirements. First, we must somehow maintain information about when
but that seems to increasingly stray away from what should be a 0-cost abstraction. Quick and dirty alternative is to just add a boolean flag to the struct now, but I’d prefer not to land code like that if there’s any way to avoid it. |
Friendly ping from triage, @sharkdp, it looks like this PR needs some additional design / implementation work. Do you think you'll be able to look into that? |
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
@TimNN I am happy to work on this, but I will probably need some help, as mentioned above. I have now pushed a new commit which resolves the merge conflicts and handles the problem in the same way as in my first commit - with an additional flag. This is a rather minimal change to fix this bug, but I agree with @nagisa that it is probably not optimal. However, I don't see how the other two discussed solutions (using |
Ping from triage @sfackler! The author needs a bit of help, could you check in on this? |
Ping from triage @sfackler! The author needs a bit of help, could you check in on this? |
Oops, sorry. This seems good to me! @bors r+ |
📌 Commit af75314 has been approved by |
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
☀️ Test successful - status-appveyor, status-travis |
This upgrades the minimum required version of Rust to 1.29 in order to fix BurntSushi#916 (Zombie processes cause `rg --files` to hang in `/proc`). See also: - Rust compiler bug ticket: rust-lang/rust#50619 - Rust compiler PR with the fix: rust-lang/rust#50630 closes BurntSushi#916
This upgrades the minimum required version of Rust to 1.29 in order to fix #288. See also: - Rust compiler bug ticket: rust-lang/rust#50619 - Rust compiler PR with the fix: rust-lang/rust#50630 closes #288
This upgrades the minimum required version of Rust to 1.29 in order to fix #288. See also: - Rust compiler bug ticket: rust-lang/rust#50619 - Rust compiler PR with the fix: rust-lang/rust#50630 closes #288
Bug rust-lang#50619 was fixed by adding an end_of_stream flag in rust-lang#50630. Unfortunately, that fix only applied to the readdir_r() path. When I switched Linux to use readdir() in rust-lang#92778, I inadvertently reintroduced the bug on that platform. Other platforms that had always used readdir() were presumably never fixed. This patch enables end_of_stream for all platforms, and adds a Linux-specific regression test that should hopefully prevent the bug from being reintroduced again.
…imulacrum fs: Fix rust-lang#50619 (again) and add a regression test Bug rust-lang#50619 was fixed by adding an end_of_stream flag in rust-lang#50630. Unfortunately, that fix only applied to the readdir_r() path. When I switched Linux to use readdir() in rust-lang#92778, I inadvertently reintroduced the bug on that platform. Other platforms that had always used readdir() were presumably never fixed. This patch enables end_of_stream for all platforms, and adds a Linux-specific regression test that should hopefully prevent the bug from being reintroduced again.
…imulacrum fs: Fix rust-lang#50619 (again) and add a regression test Bug rust-lang#50619 was fixed by adding an end_of_stream flag in rust-lang#50630. Unfortunately, that fix only applied to the readdir_r() path. When I switched Linux to use readdir() in rust-lang#92778, I inadvertently reintroduced the bug on that platform. Other platforms that had always used readdir() were presumably never fixed. This patch enables end_of_stream for all platforms, and adds a Linux-specific regression test that should hopefully prevent the bug from being reintroduced again.
Certain directories in
/proc
can cause theReadDir
iterator to loop indefinitely. We get an error code (22) when calling libc'sreaddir_r
on these directories, butentry_ptr
isNULL
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 callingnext
. Subsequent calls will returnNone
.fixes #50619