-
Notifications
You must be signed in to change notification settings - Fork 673
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
Convert common syscalls to safe I/O types #1863
Conversation
There's a lot of stuff here. Could you please annotate #1750 to say which modules you're converting? |
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
1908: Move some pure formatting changes out of #1863 r=asomers a=SUPERCILEX Co-authored-by: Alex Saveau <[email protected]>
88b6ba9
to
760ca08
Compare
Wow that was painful. 😅 I think this is finally ready for review. |
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 haven't reviewed the tests yet. But the src changes look mostly good. The one big problem I see is that you removed the Option from all of the *at functions. I don't think we want to do that.
src/unistd.rs
Outdated
@@ -438,37 +452,43 @@ pub fn dup(oldfd: RawFd) -> Result<RawFd> { | |||
/// specified fd instead of allocating a new one. See the man pages for more | |||
/// detail on the exact behavior of this function. | |||
#[inline] | |||
pub fn dup2(oldfd: RawFd, newfd: RawFd) -> Result<RawFd> { | |||
let res = unsafe { libc::dup2(oldfd, newfd) }; | |||
pub fn dup2<Fd: AsFd>(oldfd: &Fd, newfd: &mut OwnedFd) -> Result<()> { |
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.
Clever, taking OwnedFd
by borrow. But what if the user wants to supply an integer that doesn't correspond to any currently open file? That was possible with the old interface.
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.
Dang, you're right. Will fix.
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 couldn't figure out how to let you both pass in an fd by reference and by value, so I ended up adding dup*_raw
variants.
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.
so I ended up adding dup*_raw variants.
This is something unfortunate, but I think this unfortunate approach is the only way to do it, rustix does not provide something similar to unsafe fn dup2_raw()
, so one cannot specify a fd that is still not open.
And interestingly, rustix provides 3 helper functions to use dup2
with Stdin
, Stdout
and Stderr
: https://docs.rs/rustix/latest/rustix/stdio/fn.dup2_stdin.html
a56ddf2
to
3a11e20
Compare
676185c
to
4da7e7f
Compare
Also at @asomers any chance this PR can be prioritized over the other I/O safety ones? Otherwise I'm going to be stuck constantly resolving merge conflicts. |
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.
There's another possibility for the *at functions. The cap-std crate treats them as methods on an already-created file descriptor object, like this:
let etc = Dir::open_ambient_dir("/etc", ambient_authority())?;
let passwd = etc.open("passwd")?;
I think that's better than any of the proposals here. The only problems with cap-std are that it's too big. It pulls in too many dependencies, and it tries to provide a platform-independent abstraction layer. Should we do something similar?
1913: feat: I/O safety for 'sys/inotify' r=asomers a=SteveLauC #### What this PR does: 1. Changes the `fd` field of `struct Inotify` from `RawFd` to `OwnedFd` 2. Changes the interfaces of functions in the `impl Inotify {}` > The type of `self` changes from `Self` to `&mut Self`. From: ```rust pub fn add_watch<P: ?Sized + NixPath>( self, path: &P, mask: AddWatchFlags, ) -> Result<WatchDescriptor> pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> pub fn read_events(self) -> Result<Vec<InotifyEvent>> ``` To: ```rust pub fn add_watch<P: ?Sized + NixPath>( &mut self, path: &P, mask: AddWatchFlags, ) -> Result<WatchDescriptor> pub fn rm_watch(&mut self, wd: WatchDescriptor) -> Result<()> pub fn read_events(&mut self) -> Result<Vec<InotifyEvent>> ``` In the previous implementation, these functions can take `self` by value as `struct Inotify` [was `Copy`](https://docs.rs/nix/latest/nix/sys/inotify/struct.Inotify.html#impl-Copy-for-Inotify). With the changes in `1` applied, `struct Inotify` is no longer `Copy`, so we have to take `self` by reference. ------- Blocks until the merge of #1863 as this PR needs `read(2)` to be I/O-safe. 1926: feat: I/O safety for 'sys/sendfile' r=asomers a=SteveLauC #### What this PR does: 1. Adds I/O safety for module `sys/sendfile`. 1927: feat: I/O safety for 'sys/statvfs' r=asomers a=SteveLauC #### What this PR does: 1. Adds I/O safety for module `sys/statvfs`. 1931: feat: I/O safety for 'sys/uid' & 'sched' r=asomers a=SteveLauC #### What this PR does: Adds I/O safety for modules: 1. `sys/uio` 2. `sched` 1933: feat: I/O safety for 'sys/timerfd' r=asomers a=SteveLauC #### What this PR does: 1. Adds I/O safety for module `sys/timerfd`. Co-authored-by: Steve Lau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
fa23312
to
db93263
Compare
I had considered this a while back and think the conclusion was that I didn't like it because it'd pollute the i32 namespace. Now it would pollute Not sure where that leaves this PR... maybe we can keep |
Yeah, I wasn't thinking about adding a method to |
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
db93263
to
e89b47a
Compare
Gotya, cool. I'm planning on playing around with that tomorrow. Regarding Illumos, I'd prefer doing the nice thing for everybody else and adding a cfg exception for them.
A month ago, I just used Nix, but over the past few weeks, I've fully migrated over to rustix for the perf improvements of raw syscalls. So why stick around? Originally, it was because I had open PRs here and it felt wrong to close everything and be like, "So long, suckers!" 😆 Anyway, now I'm sticking around because between you and Dan we're generating some interesting ideas that I don't think would have come about without the cross pollination. |
This smells like it should be a newtype to me. Make it implement AsFd or From and it should work. |
} | ||
|
||
#[inline] | ||
pub fn dup2_raw<Fd1: AsFd, Fd2: AsRawFd + IntoRawFd>( |
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.
If this operates on a RawFd
it should be unsafe
. Otherwise it could be passed a fd corresponding to a file open elsewhere, and break soundness assumptions in the code using it.
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.
No, that's why it requires IntoRawFd
. If the call fails, you had to pass in your FD by value which means it gets dropped inside dup2_raw
. If it succeeds, we give you back the same fd. Of course if you don't want the safety stuff, then you pass in a RawFd
and IntoRawFd
will do nothing.
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.
In the current Rust version RawFd
implements AsRawFd
and IntoRawFd
, so this API can easily be used with an arbitrary integer. So you could for instance call close(dup2_raw(some_fd, 0).unwrap())
. Allowing safe code to close stdin or any other fd. This shouldn't be possible in safe code without an OwnedFd
.
This function is only safe if the caller either owns fd2, or knows the fd isn't used.
So I think the function should be an unsafe fn
, with a doc comment describing when it is safe.
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.
Maybe? When to require unsafe is unclear IMO. BTW, your example can just be close(0)
—no need for dup. I think our goal should be to not break BorrowedFd or OwnedFd. If you want to do weird stuff with RawFd, then I feel like that's up to you.
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.
https://rust-lang.github.io/rfcs/3128-io-safety.html
Functions accepting arbitrary raw I/O handle values (RawFd, RawHandle, or RawSocket) should be unsafe if they can lead to any I/O being performed on those handles through safe APIs.
Basically any function that can operate on a RawFd
should be unsafe. So to conform to IO safety, close
should either be unsafe, or it should take a OwnedFd
and consume it.
This is definitely awkward for a crate like nix
though, since conforming to this necessitates breaking changes to basically all of the API. Perhaps these functions in nix
should have been unsafe already, but ideally Rust would have had clear rules about IO safety before 1.0.
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 definitely awkward for a crate like nix though
Yeah, I think that's the crux of the matter. Nix is trying to be more low-level than the stdlib, so requiring unsafe is probably going to be annoying. I also think you can argue that we're following the definition. It says "can lead to" which to me means that if you store the RawFd somewhere and then use it later (like in a BorrowedFd or File), the function that does the storing should be marked unsafe, but if you immediately use the RawFd then you're good. Otherwise anytime a RawFd is even mentioned you'd need unsafe which doesn't make sense to me.
All of that said, I wouldn't be opposed to marking close
as unsafe. Interestingly enough, dup is perfectly safe according to "Rust code has I/O safety if it's not possible for that code to cause invalid I/O operations" because anything you throw at dup will work (if the fd doesn't exist, it'll be allocated, if it does exist, it'll be closed).
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.
Other than the breaking API changes, and interop with other things using RawFd
, it seems like it should be fairly ergonomic to work with a safe API that uses OwnedFd
/BorrowedFd
/AsFd
and mostly doesn't expose RawFd
anywhere in the API (dup2_raw
is a special case here).
Interestingly enough, dup is perfectly safe according to "Rust code has I/O safety if it's not possible for that code to cause invalid I/O operations"Interestingly enough, dup is perfectly safe according to "Rust code has I/O safety if it's not possible for that code to cause invalid I/O operations"
This is subtle, but a safe dup
taking a RawFd
and returning an OwnedFd
could be used to trigger memory unsafety with the sort of things envisioned in the IO safety RFC. In particular:
And in specialized circumstances, violating I/O safety could even lead to violating memory safety. For example, in theory it should be possible to make a safe wrapper around an mmap of a file descriptor created by Linux's memfd_create system call and pass &[u8]s to safe Rust, since it's an anonymous open file which other processes wouldn't be able to access. However, without I/O safety, and without permanently sealing the file, other code in the program could accidentally call write or ftruncate on the file descriptor, breaking the memory-safety invariants of &[u8].
So safe code shouldn't be able to write
or truncate
an arbitrary fd, which could break immutability assumptions of something mmapping that fd. dup
is also a problem, because safe code could write or truncate given an OwnedFd
, which will have the same issue as write or truncate to the original fd.
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.
Sure, but then everything is unsafe because you can just open /proc/self/mem
. I think we have to take a pragmatic approach here and making dup unsafe because someone might be messing around with mmap is overkill.
@asomers see bytecodealliance/rustix#478 (comment). I want to try your idea, but not in this PR. Regarding this PR: please let me know what needs to change for it to be merged (I'll resolve conflicts then). I think AsDirFd should be left in, though I'd be willing to temporarily rip it out. I can also drop the dup commit, but I think as long as the commits are merged as is this PR is fine and we can just do fixups before release if necessary. Regarding AsDirFd, I'll make a new PR after this one is merged that replaces it with the idea discussed here and in rustix. Regarding making openat an extension method, I think that's a bad idea: there's no symmetry to be found. If we make openat an extension method, why stop there? Open can be an extension on paths. Close can be an extension on FDs. But then what do you do about methods that have multiple paths/fds or mix the two? The whole thing becomes a mess, so let's just keep it simple and use top-level functions. |
This gives us file descriptor types. nix-rust/nix#1863 BUG=b:233174528 TEST=CQ Change-Id: Ida81204a41d9e2f8b81afa3d0eb729e81a853f95 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/5599982 Tested-by: [email protected] <[email protected]> Commit-Queue: Li-Yu Yu <[email protected]> Reviewed-by: Chih-Yang Hsia <[email protected]>
Close as this has been superseded by other I/O safety PRs: https://github.com/nix-rust/nix/tree/d60f710c31394b3ee7af2a1be0f37a713a9565bd/changelog |
Works towards #1750. Fixes #1850.
I went the lazy route and pulled the plug... trying to feature gate everything seemed way too complicated.