-
Notifications
You must be signed in to change notification settings - Fork 13.6k
add more explicit I/O safety documentation #114780
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
Changes from 5 commits
a473e95
b2b225e
334a54c
55f18be
4da0811
03c28d5
85e6e82
e9eca7c
2cb9d3d
6d65379
1290cd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,7 +5,7 @@ | |||||||||
//! the [`Read`] and [`Write`] traits, which provide the | ||||||||||
//! most general interface for reading and writing input and output. | ||||||||||
//! | ||||||||||
//! # Read and Write | ||||||||||
//! ## Read and Write | ||||||||||
//! | ||||||||||
//! Because they are traits, [`Read`] and [`Write`] are implemented by a number | ||||||||||
//! of other types, and you can implement them for your types too. As such, | ||||||||||
|
@@ -238,13 +238,53 @@ | |||||||||
//! contract. The implementation of many of these functions are subject to change over | ||||||||||
//! time and may call fewer or more syscalls/library functions. | ||||||||||
//! | ||||||||||
//! ## I/O Safety | ||||||||||
//! | ||||||||||
//! Rust follows an [I/O safety] discipline that is comparable to its memory safety discipline. This | ||||||||||
//! means that file descriptors can be *exclusively owned*. (Here, "file descriptor" is meant to | ||||||||||
//! subsume similar concepts that exist across a wide range of operating systems even if they might | ||||||||||
//! use a different name, such as "handle".) An exclusively owned file descriptor is one that no | ||||||||||
//! other code is allowed to close, but the owner is allowed to close it any time. A type that owns | ||||||||||
//! its file descriptor should usually close it in its `drop` function. Types like [`File`] generally own | ||||||||||
//! their file descriptor. Similarly, file descriptors can be *borrowed*. This indicates that the | ||||||||||
//! file descriptor will not be closed for the lifetime of the borrow, but it does *not* imply any | ||||||||||
//! right to close this file descriptor, since it will likely be owned by someone else. | ||||||||||
//! | ||||||||||
//! The platform-specific parts of the Rust standard library expose types that reflect these | ||||||||||
//! concepts, see [`os::unix`] and [`os::windows`]. | ||||||||||
//! | ||||||||||
//! To uphold I/O safety, it is crucial that no code closes file descriptors it does not own. In | ||||||||||
//! other words, a safe function that takes a regular integer, treats it as a file descriptor, and | ||||||||||
//! closes it, is *unsound*. | ||||||||||
//! | ||||||||||
//! Not upholding I/O safety and closing a file descriptor without proof of ownership can lead to | ||||||||||
//! misbehavior and even Undefined Behavior in code that relies on ownership of its file | ||||||||||
//! descriptors: the closed file descriptor could be re-allocated to some other library (such as the | ||||||||||
//! allocator or a memory mapping library) and now accessing the file descriptor will interfere in | ||||||||||
//! arbitrarily destructive ways with that other library. | ||||||||||
//! | ||||||||||
//! Note that this does not talk about performing other operations on the file descriptor, such as | ||||||||||
//! reading or writing. For example, on Unix, the [`OwnedFd`] and [`BorrowedFd`] types from the | ||||||||||
//! standard library do *not* exclude that there is other code that reads or writes the same | ||||||||||
//! underlying object, and indeed there exist safe functions like `BorrowedFd::try_clone_to_owned` | ||||||||||
//! that can be used to read or write an object even after the end of the borrow. However, user code | ||||||||||
//! might want to rely on keeping the object behind a file descriptor completely private and | ||||||||||
//! protected against reads or writes from other parts of the program. Whether that is sound is | ||||||||||
//! [currently unclear](https://github.com/rust-lang/rust/issues/114167). Certainly, `OwnedFd` as a | ||||||||||
//! type does not provide any promise that the underlying file descriptor has not been cloned. | ||||||||||
sunfishcode marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
//! | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thought: can we also list some of the risks of violating IO safety? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ideally, exhaustively if possible roughly: if you are accessing an fd after it has been closed, it might have been reallocated to belong to someone else, which might be the allocator, or memory mapping libraries, or something else. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a paragraph. However, without full file description encapsulation, I am not sure if there even is an example where this can truly lead to UB? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably say a bit about what that means, too, similar to https://doc.rust-lang.org/nightly/std/os/unix/io/index.html#procselfmem-and-similar-os-features? Basically we are saying Rust programs must not abuse these APIs, so "out of scope" is not a free pass to use them to circumvent everything. They can be used for debugging/tracing only. |
||||||||||
//! [`File`]: crate::fs::File | ||||||||||
//! [`TcpStream`]: crate::net::TcpStream | ||||||||||
//! [`io::stdout`]: stdout | ||||||||||
//! [`io::Result`]: self::Result | ||||||||||
//! [`?` operator]: ../../book/appendix-02-operators.html | ||||||||||
//! [`Result`]: crate::result::Result | ||||||||||
//! [`.unwrap()`]: crate::result::Result::unwrap | ||||||||||
//! [I/O safety]: https://rust-lang.github.io/rfcs/3128-io-safety.html | ||||||||||
ChrisDenton marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
//! [`os::unix`]: ../os/unix/io/index.html | ||||||||||
//! [`os::windows`]: ../os/windows/io/index.html | ||||||||||
//! [`OwnedFd`]: ../os/fd/struct.OwnedFd.html | ||||||||||
//! [`BorrowedFd`]: ../os/fd/struct.BorrowedFd.html | ||||||||||
|
||||||||||
#![stable(feature = "rust1", since = "1.0.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.
My intent with I/O safety is a stronger rule than just "only the owner is allowed to close". I also intend "and no one can do any operation, including read, write, or anything else, with a file descriptor that is closed".
Without this stronger rule, it would be impossible to ever encapsulate a file description, because things like
dup(rand())
would become permitted, and that would preclude some useful use cases.Uh oh!
There was an error while loading. Please reload this page.
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.
Given that I can
dup
a file descriptor that I have merely borrowed, most of these usecases will already not work -- at least they won't work withOwnedFd
. I was hoping we'd have that "you cannot write an FD that you do not own", but we do not have that guarantee withOwnedFd
and it is unclear if there is any way to protect an FD against being written-to by other parts of the code.The OS will reliably return EBADFD on any operation if the FD is closed, so there's not really any point in disallowing this that I can see.
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.
File description encapsulation requires encapsulating the
OwnedFd
and not implementingAsFd
or handing out aBorrowedFd
. So it takes some deliberate doing, but it is doable, as long as we disallow things likedup(rand())
andread(rand(), ...)
and so on.The OS is documented to return
EBADF
, however any time you get that error on a closed fd, it only means you got lucky that no code, perhaps on another thread racing with you, did eg.File::open
and reused the fd number.Uh oh!
There was an error while loading. Please reload this page.
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.
(let's merge this with the other thread about file object encapsulation)