Skip to content
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

[android] Add support for android's file descriptor ownership tagging to libstd. #74860

Closed
wants to merge 2 commits into from

Conversation

jmgao
Copy link

@jmgao jmgao commented Jul 28, 2020

Android has started investigating using Rust for system components, and one of the biggest historical sources of bugs we've had in cross-language interop has been caused by confusion around file descriptor ownership, since you're forced to pass file descriptors across language boundaries as a bare int without any type information about whether ownership is being transferred. The bugs this results in are horrible to debug, because they're nondeterministic and manifest as impossible behavior occurring on different threads. (e.g. [1], [2])

We've added some facilities to our libc to detect/prevent this class of bugs. Basically, user code can tell libc that a file descriptor can only be closed with a specified token, and to abort/log an error if someone tries to close the fd without the token (the behavior is process-wide and configurable at runtime, but the default on Android R (API level 30) and beyond is to abort). We've found dozens of bugs in Android with this, and hopefully prevented many more from ever being checked in.

This PR makes libstd use this on android when available. It potentially changes behavior, but only for code that's either broken, or very weird (e.g. File::from_raw_fd(fd).into_raw_fd() sequences which construct a File that owns the fd, and then removes it from the File before drop), and also only for unsafe code, I believe.

(also, as another commit in this PR, I improved the error handling on non-android close. Let me know if I should split this out to a separate PR) (whoops, didn't actually build this commit: I'll do this as a followup)

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @nikomatsakis (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.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 28, 2020
Android's libc provides facilities to mark file descriptor ownership, in
order to prevent people from closing file descriptors that they don't
own. Add support for this to FileDesc, which should cover all of the
relevant file descriptor-owning types in Rust.
@jonas-schievink jonas-schievink added the O-android Operating system: Android label Jul 28, 2020
@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jul 29, 2020

I left a comment about this on Zulip -- I'm not sure the proper procedure here, but I'm inclined to land this PR. Probably it should be tagged with relnotes though.

@nikomatsakis
Copy link
Contributor

Thinking a bit about it, since it is a user-visible change, I think an FCP is probably appropriate. @jmgao I'm curious if you could give some examples of what kinds of bugs you expect to find with this change -- e.g., would it help to detect broken code from users? Only if they are using the from_raw_fd API?

@nikomatsakis
Copy link
Contributor

Also, cc @rust-lang/libs -- this seems to lie somewhere on the boundary of "implementation detail" and "public facing API change", so maybe y'all want to weigh in?

@nikomatsakis nikomatsakis added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 29, 2020
@Amanieu
Copy link
Member

Amanieu commented Jul 29, 2020

Should into_raw_fd reset the tag to 0? I think it should since you're explicitly saying that the fd is no longer managed by Rust.

@jmgao
Copy link
Author

jmgao commented Jul 29, 2020

Thinking a bit about it, since it is a user-visible change, I think an FCP is probably appropriate. @jmgao I'm curious if you could give some examples of what kinds of bugs you expect to find with this change -- e.g., would it help to detect broken code from users? Only if they are using the from_raw_fd API?

Yeah, it'll detect when user code incorrectly transfers ownership, either in (via from_raw_fd) or out (via as_raw_fd, either closing it manually in Rust, or more likely, IMO, returning it across an FFI boundary and calling close on the other side).

Here's an example of a bug in Android that's the latter scenario: an fd is owned by a Java ParcelFIleDescriptor, and some C++ code grabbed it and passed it directly to some other code that expected to own the fd.

Should into_raw_fd reset the tag to 0? I think it should since you're explicitly saying that the fd is no longer managed by Rust.

Oops, yes, I'll fix this shortly. Before merging this PR in general, I need to find somewhere to add some tests, but I'm not entirely clear on where they should live, since they're Android-only (and behavior will also depend on what version of Android it's running on). Any suggestions?

@Amanieu
Copy link
Member

Amanieu commented Jul 30, 2020

The documentation for as_raw_fd should be updated to say that behavior is undefined if the returned fd is closed even if mem::forget is used. It should mention Android's ownership tagging as an example and recommend the use of into_raw_fd instead.

The documentation for from_raw_fd should also state that the fd must not currently be owned by any other Rust fd wrapper (e.g. File). Again Android's fd tagging should be mentioned as an example of how this can cause issues in practice.

@pnkfelix
Copy link
Member

pnkfelix commented Aug 6, 2020

discussed at today's T-compiler triage meeting.

The T-compiler members present at the meeting didn't have any strong opinions here. We agreed with @nikomatsakis that this warrants an FCP of some sort.

There was general consensus that this is, at its heart, a question of what the publicly specified API for Rust's File should be; the actual implementation code (which T-compiler maintains) is pretty trivial.

So we collectively decided at the meeting that while this should require an FCP, it should really solely require an FCP of the members of T-libs; T-compiler is going to opt out of participating in the FCP.

Thus, I am removing the T-compiler label, keeping the T-libs label, and I will go see if I can find a T-libs member to start up the FCP process for this PR.

@pnkfelix pnkfelix added needs-fcp This change is insta-stable, so needs a completed FCP to proceed. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 6, 2020
@Amanieu
Copy link
Member

Amanieu commented Aug 6, 2020

@rfcbot fcp merge

@rfcbot
Copy link

rfcbot commented Aug 6, 2020

Team member @Amanieu has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Aug 6, 2020
@jmgao
Copy link
Author

jmgao commented Aug 7, 2020

The documentation for as_raw_fd should be updated to say that behavior is undefined if the returned fd is closed even if mem::forget is used. It should mention Android's ownership tagging as an example and recommend the use of into_raw_fd instead.

The documentation for from_raw_fd should also state that the fd must not currently be owned by any other Rust fd wrapper (e.g. File). Again Android's fd tagging should be mentioned as an example of how this can cause issues in practice.

Somewhat of a nitpicky question: is "undefined behavior" the proper phrase to use for this? It's definitely incorrect, but it can't lead to the compiler using the fact that you did that to make assumptions that transitively breaks code elsewhere. The phrasing I'm thinking of now is something along the lines of:

AsRawFd:

 This method does not pass ownership of the raw file descriptor to the caller. The descriptor is only guaranteed to be valid while the original object has not yet been destroyed.
+Do not attempt to take ownership of this file descriptor (e.g. by using `FromRawFd`). On Android API level 30 and beyond, file descriptor ownership is enforced, which will lead to the process aborting if this occurs.

FromRawFd:

 This function consumes ownership of the specified file descriptor. The returned object will take responsibility for closing it when the object goes out of scope.
+The file descriptor consumed must not be already owned by another object. On Android API level 30 and beyond, file descriptor ownership is enforced, which will lead to the process aborting if this occurs.

@Amanieu
Copy link
Member

Amanieu commented Aug 7, 2020

Undefined behavior doesn't have to be linked to the compiler, it can come from the library as well: in this case it means that behavior may change in the future and may lead to unsoundness via unsafe assumptions (i.e. writing to the wrong FD).

I think that undefined behavior is appropriate in this case: Android happens to catch these cases, but other platforms do not.

@crlf0710 crlf0710 added S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). and removed S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). labels Aug 28, 2020
@jonas-schievink jonas-schievink added the relnotes Marks issues that should be documented in the release notes of the next release. label Sep 13, 2020
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Nov 18, 2020
@rfcbot
Copy link

rfcbot commented Nov 18, 2020

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC will be merged soon.

@rfcbot rfcbot added the to-announce Announce this issue on triage meeting label Nov 18, 2020
@nikomatsakis
Copy link
Contributor

Hey @rust-lang/libs, I see that the FCP has completed, but I think this concern raised by @the8472 merits an official response:

That would conflict with temporarily constructing a wrapper to use standard IO APIs from another wrapper type which doesn't support those APIs.

My impression is that we don't have a workaround or alternative for this pattern, so I'm wondering whether you all consider this a blocking concern to be resolved.

@m-ou-se
Copy link
Member

m-ou-se commented Nov 18, 2020

Thanks for the ping @nikomatsakis. I've added it to the agenda for the libs meeting, which starts in a few hours.

@m-ou-se
Copy link
Member

m-ou-se commented Nov 18, 2020

A few of the places that use ManuallyDrop<File> or ManuallyDrop<FileDesc> or ManuallyDrop<TcpStream> (etc.) as a stand in for a 'borrowed file descriptor':

In std:

In wasmtime:

In other crates:

And a few places that use File::from_raw_fd for file descriptors that are not strictly 'owned':

There's probably more, but it's a bit hard to search for.

@m-ou-se
Copy link
Member

m-ou-se commented Nov 18, 2020

It seems to me that because of

impl io::Write for Stdout {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
ManuallyDrop::new(FileDesc::new(libc::STDOUT_FILENO)).write(buf)
}

even a simple println!("hello"); println!("world"); would already break, as it calls write twice, which would end up tagging stdout twice. Am I missing something?

@jmgao
Copy link
Author

jmgao commented Nov 18, 2020

My impression is that we don't have a workaround or alternative for this pattern, so I'm wondering whether you all consider this a blocking concern to be resolved.

IMO this is definitely a blocking concern. I think the behavior proposed by @the8472 might be the best option:

Perhaps from_raw_fd should be changed to only set a tag if none is present and otherweise store tag: 0 in FileDesc and then panic on drop if it is 0. That would force the use of ManuallyDrop for such temporary conversions.

@KodrAus
Copy link
Contributor

KodrAus commented Nov 18, 2020

@rfcbot cancel

@rfcbot
Copy link

rfcbot commented Nov 18, 2020

@KodrAus proposal cancelled.

@rfcbot rfcbot removed the disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. label Nov 18, 2020
@m-ou-se m-ou-se removed the finished-final-comment-period The final comment period is finished for this PR / Issue. label Nov 18, 2020
@the8472
Copy link
Member

the8472 commented Nov 18, 2020

This API is also new. Since CI is setup to only test against a fairly old minimum API level this change won't be exercised in CI and thus it won't catch broken uses.

@m-ou-se m-ou-se added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed I-nominated S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). labels Nov 25, 2020
@crlf0710
Copy link
Member

@jmgao Ping from triage! What's the next steps here?

@crlf0710 crlf0710 added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Dec 11, 2020
@crlf0710
Copy link
Member

@jmgao Ping from triage, i'm closing this due to inactivity. Feel free to reopen or create a new PR when you have time to work on this again. Thanks!

@workingjubilee
Copy link
Member

With the recent IO safety concept being introduced into std, perhaps this has a reasonable route forward again?

@apiraino
Copy link
Contributor

@rustbot label -to-announce

@rustbot rustbot removed the to-announce Announce this issue on triage meeting label Mar 15, 2022
@apiraino
Copy link
Contributor

apiraino commented Mar 15, 2022

@rustbot label +to-announce

@rustbot rustbot added the to-announce Announce this issue on triage meeting label Mar 15, 2022
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Mar 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-fcp This change is insta-stable, so needs a completed FCP to proceed. O-android Operating system: Android relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.