From a473e95786fa2a1391903f2b3b31af5ae300a2d5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 13 Aug 2023 14:36:06 +0200 Subject: [PATCH 01/11] add more explicit I/O safety documentation --- library/std/src/io/mod.rs | 36 ++++++++++++++++++++++++++- library/std/src/os/fd/owned.rs | 12 ++++++--- library/std/src/os/fd/raw.rs | 5 +++- library/std/src/os/fortanix_sgx/io.rs | 4 ++- library/std/src/os/solid/io.rs | 4 ++- library/std/src/os/unix/io/mod.rs | 3 ++- library/std/src/os/windows/io/mod.rs | 3 ++- 7 files changed, 57 insertions(+), 10 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 5c1d2d8f46cd4..d2422cc1e4003 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -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,6 +238,35 @@ //! 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 exclusivley 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 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*. +//! +//! 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. +//! //! [`File`]: crate::fs::File //! [`TcpStream`]: crate::net::TcpStream //! [`io::stdout`]: stdout @@ -245,6 +274,11 @@ //! [`?` 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 +//! [`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")] diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 2180d2974d5ae..7861f1a3dfa7b 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -15,8 +15,9 @@ use crate::sys_common::{AsInner, FromInner, IntoInner}; /// A borrowed file descriptor. /// -/// This has a lifetime parameter to tie it to the lifetime of something that -/// owns the file descriptor. +/// This has a lifetime parameter to tie it to the lifetime of something that owns the file +/// descriptor. For the duration of that lifetime, it is guaranteed that nobody will close the file +/// descriptor. /// /// This uses `repr(transparent)` and has the representation of a host file /// descriptor, so it can be used in FFI in places where a file descriptor is @@ -42,7 +43,8 @@ pub struct BorrowedFd<'fd> { /// An owned file descriptor. /// -/// This closes the file descriptor on drop. +/// This closes the file descriptor on drop. It is guarantees that nobody else will close the file +/// descriptor. /// /// This uses `repr(transparent)` and has the representation of a host file /// descriptor, so it can be used in FFI in places where a file descriptor is @@ -155,7 +157,9 @@ impl FromRawFd for OwnedFd { /// # Safety /// /// The resource pointed to by `fd` must be open and suitable for assuming - /// ownership. The resource must not require any cleanup other than `close`. + /// [ownership][io-safety]. The resource must not require any cleanup other than `close`. + /// + /// [io-safety]: io#io-safety #[inline] unsafe fn from_raw_fd(fd: RawFd) -> Self { assert_ne!(fd, u32::MAX as RawFd); diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index 592e072ad908a..f80669ec708cc 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -84,7 +84,10 @@ pub trait FromRawFd { /// /// # Safety /// - /// The `fd` passed in must be a valid and open file descriptor. + /// The `fd` passed in must be an [owned file descriptor][io-safety]; + /// in particular, it must be valid and open. + /// + /// [io-safety]: io#io-safety /// /// # Example /// diff --git a/library/std/src/os/fortanix_sgx/io.rs b/library/std/src/os/fortanix_sgx/io.rs index 7223ade68158f..a7c22655653cd 100644 --- a/library/std/src/os/fortanix_sgx/io.rs +++ b/library/std/src/os/fortanix_sgx/io.rs @@ -31,10 +31,12 @@ pub trait FromRawFd { /// Constructs a new instance of `Self` from the given raw file /// descriptor and metadata. /// - /// This function **consumes ownership** of the specified file + /// This function **consumes [ownership][io-safety]** of the specified file /// descriptor. The returned object will take responsibility for closing /// it when the object goes out of scope. /// + /// [io-safety]: crate::io#io-safety + /// /// This function is also unsafe as the primitives currently returned /// have the contract that they are the sole owner of the file /// descriptor they are wrapping. Usage of this function could diff --git a/library/std/src/os/solid/io.rs b/library/std/src/os/solid/io.rs index 33cc5a015b5dc..5e6bf70ed4d22 100644 --- a/library/std/src/os/solid/io.rs +++ b/library/std/src/os/solid/io.rs @@ -27,10 +27,12 @@ pub trait FromRawFd { /// Constructs a new instance of `Self` from the given raw file /// descriptor. /// - /// This function **consumes ownership** of the specified file + /// This function **consumes [ownership][io-safety]** of the specified file /// descriptor. The returned object will take responsibility for closing /// it when the object goes out of scope. /// + /// [io-safety]: crate::io#io-safety + /// /// This function is also unsafe as the primitives currently returned /// have the contract that they are the sole owner of the file /// descriptor they are wrapping. Usage of this function could diff --git a/library/std/src/os/unix/io/mod.rs b/library/std/src/os/unix/io/mod.rs index 25b5dbff14f30..b3070a61b6891 100644 --- a/library/std/src/os/unix/io/mod.rs +++ b/library/std/src/os/unix/io/mod.rs @@ -6,7 +6,7 @@ //! //! This module provides three types for representing file descriptors, //! with different ownership properties: raw, borrowed, and owned, which are -//! analogous to types used for representing pointers: +//! analogous to types used for representing pointers. These types realize the Unix version of [I/O safety]. //! //! | Type | Analogous to | //! | ------------------ | ------------ | @@ -74,6 +74,7 @@ //! necessary to use *sandboxing*, which is outside the scope of `std`. //! //! [`BorrowedFd<'a>`]: crate::os::unix::io::BorrowedFd +//! [I/O safety]: crate::io#io-safety #![stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/os/windows/io/mod.rs b/library/std/src/os/windows/io/mod.rs index e2a401fb6962b..9c304b8e990fa 100644 --- a/library/std/src/os/windows/io/mod.rs +++ b/library/std/src/os/windows/io/mod.rs @@ -6,7 +6,7 @@ //! //! This module provides three types for representing raw handles and sockets //! with different ownership properties: raw, borrowed, and owned, which are -//! analogous to types used for representing pointers: +//! analogous to types used for representing pointers. These types realize the Windows version of [I/O safety]. //! //! | Type | Analogous to | //! | ---------------------- | ------------ | @@ -47,6 +47,7 @@ //! //! [`BorrowedHandle<'a>`]: crate::os::windows::io::BorrowedHandle //! [`BorrowedSocket<'a>`]: crate::os::windows::io::BorrowedSocket +//! [I/O safety]: crate::io#io-safety #![stable(feature = "rust1", since = "1.0.0")] From b2b225e1d1e2bcdd2fabd8765b228b3480f3a998 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 14 Aug 2023 08:47:33 +0200 Subject: [PATCH 02/11] sync the various FromRawFd trait docs, and remove 'valid' --- library/std/src/os/fd/raw.rs | 2 +- library/std/src/os/fortanix_sgx/io.rs | 23 ++++++++++++++--------- library/std/src/os/solid/io.rs | 22 +++++++++++++--------- library/std/src/os/windows/io/raw.rs | 6 ++++-- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index f80669ec708cc..ef896ea95c9c9 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -85,7 +85,7 @@ pub trait FromRawFd { /// # Safety /// /// The `fd` passed in must be an [owned file descriptor][io-safety]; - /// in particular, it must be valid and open. + /// in particular, it must be open. /// /// [io-safety]: io#io-safety /// diff --git a/library/std/src/os/fortanix_sgx/io.rs b/library/std/src/os/fortanix_sgx/io.rs index a7c22655653cd..7e57435b65ccd 100644 --- a/library/std/src/os/fortanix_sgx/io.rs +++ b/library/std/src/os/fortanix_sgx/io.rs @@ -31,17 +31,22 @@ pub trait FromRawFd { /// Constructs a new instance of `Self` from the given raw file /// descriptor and metadata. /// - /// This function **consumes [ownership][io-safety]** of the specified file - /// descriptor. The returned object will take responsibility for closing - /// it when the object goes out of scope. + /// This function is typically used to **consume ownership** of the + /// specified file descriptor. When used in this way, the returned object + /// will take responsibility for closing it when the object goes out of + /// scope. /// - /// [io-safety]: crate::io#io-safety + /// However, consuming ownership is not strictly required. Use a + /// [`From::from`] implementation for an API which strictly + /// consumes ownership. /// - /// This function is also unsafe as the primitives currently returned - /// have the contract that they are the sole owner of the file - /// descriptor they are wrapping. Usage of this function could - /// accidentally allow violating this contract which can cause memory - /// unsafety in code that relies on it being true. + /// # Safety + /// + /// The `fd` passed in must be an [owned file descriptor][io-safety]; + /// in particular, it must be open. + // FIXME: say something about `metadata`. + /// + /// [io-safety]: io#io-safety #[unstable(feature = "sgx_platform", issue = "56975")] unsafe fn from_raw_fd(fd: RawFd, metadata: Self::Metadata) -> Self; } diff --git a/library/std/src/os/solid/io.rs b/library/std/src/os/solid/io.rs index 5e6bf70ed4d22..f82034663d4e6 100644 --- a/library/std/src/os/solid/io.rs +++ b/library/std/src/os/solid/io.rs @@ -27,17 +27,21 @@ pub trait FromRawFd { /// Constructs a new instance of `Self` from the given raw file /// descriptor. /// - /// This function **consumes [ownership][io-safety]** of the specified file - /// descriptor. The returned object will take responsibility for closing - /// it when the object goes out of scope. + /// This function is typically used to **consume ownership** of the + /// specified file descriptor. When used in this way, the returned object + /// will take responsibility for closing it when the object goes out of + /// scope. /// - /// [io-safety]: crate::io#io-safety + /// However, consuming ownership is not strictly required. Use a + /// [`From::from`] implementation for an API which strictly + /// consumes ownership. /// - /// This function is also unsafe as the primitives currently returned - /// have the contract that they are the sole owner of the file - /// descriptor they are wrapping. Usage of this function could - /// accidentally allow violating this contract which can cause memory - /// unsafety in code that relies on it being true. + /// # Safety + /// + /// The `fd` passed in must be an [owned file descriptor][io-safety]; + /// in particular, it must be open. + /// + /// [io-safety]: io#io-safety unsafe fn from_raw_fd(fd: RawFd) -> Self; } diff --git a/library/std/src/os/windows/io/raw.rs b/library/std/src/os/windows/io/raw.rs index 1759e2e7f3f91..770583a9ce3e0 100644 --- a/library/std/src/os/windows/io/raw.rs +++ b/library/std/src/os/windows/io/raw.rs @@ -62,7 +62,7 @@ pub trait FromRawHandle { /// # Safety /// /// The `handle` passed in must: - /// - be a valid an open handle, + /// - be an [owned handle][io-safety]; in particular, it must be open. /// - be a handle for a resource that may be freed via [`CloseHandle`] /// (as opposed to `RegCloseKey` or other close functions). /// @@ -71,6 +71,7 @@ pub trait FromRawHandle { /// /// [`CloseHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle /// [here]: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443 + /// [io-safety]: io#io-safety #[stable(feature = "from_raw_os", since = "1.1.0")] unsafe fn from_raw_handle(handle: RawHandle) -> Self; } @@ -207,10 +208,11 @@ pub trait FromRawSocket { /// # Safety /// /// The `socket` passed in must: - /// - be a valid an open socket, + /// - be an [owned socket][io-safety]; in particular, it must be open. /// - be a socket that may be freed via [`closesocket`]. /// /// [`closesocket`]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-closesocket + /// [io-safety]: io#io-safety #[stable(feature = "from_raw_os", since = "1.1.0")] unsafe fn from_raw_socket(sock: RawSocket) -> Self; } From 334a54cd83191f38ad8046ed94c45de735c86c65 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 14 Aug 2023 08:50:32 +0200 Subject: [PATCH 03/11] typos --- library/std/src/io/mod.rs | 2 +- library/std/src/os/fd/owned.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index d2422cc1e4003..c77a6e323c888 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -243,7 +243,7 @@ //! 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 exclusivley owned file descriptor is one that no +//! 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 close it in its `drop` function. Types like [`File`] generally own //! their file descriptor. Similarly, file descriptors can be *borrowed*. This indicates that the diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 7861f1a3dfa7b..81106d6c62c3f 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -43,7 +43,7 @@ pub struct BorrowedFd<'fd> { /// An owned file descriptor. /// -/// This closes the file descriptor on drop. It is guarantees that nobody else will close the file +/// This closes the file descriptor on drop. It is guaranteed that nobody else will close the file /// descriptor. /// /// This uses `repr(transparent)` and has the representation of a host file From 55f18beddd8a74034a402fae173eb74e046cedaa Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 14 Aug 2023 08:59:37 +0200 Subject: [PATCH 04/11] wording; and explain some of the possible consequences of violating io-safety --- library/std/src/io/mod.rs | 8 +++++++- library/std/src/os/unix/io/mod.rs | 2 +- library/std/src/os/windows/io/mod.rs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index c77a6e323c888..5a9a50679574a 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -245,7 +245,7 @@ //! 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 close it in its `drop` function. Types like [`File`] generally own +//! 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. @@ -257,6 +257,12 @@ //! 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 diff --git a/library/std/src/os/unix/io/mod.rs b/library/std/src/os/unix/io/mod.rs index b3070a61b6891..f49aebefd20ca 100644 --- a/library/std/src/os/unix/io/mod.rs +++ b/library/std/src/os/unix/io/mod.rs @@ -6,7 +6,7 @@ //! //! This module provides three types for representing file descriptors, //! with different ownership properties: raw, borrowed, and owned, which are -//! analogous to types used for representing pointers. These types realize the Unix version of [I/O safety]. +//! analogous to types used for representing pointers. These types reflect the Unix version of [I/O safety]. //! //! | Type | Analogous to | //! | ------------------ | ------------ | diff --git a/library/std/src/os/windows/io/mod.rs b/library/std/src/os/windows/io/mod.rs index 9c304b8e990fa..3d4bb96d45802 100644 --- a/library/std/src/os/windows/io/mod.rs +++ b/library/std/src/os/windows/io/mod.rs @@ -6,7 +6,7 @@ //! //! This module provides three types for representing raw handles and sockets //! with different ownership properties: raw, borrowed, and owned, which are -//! analogous to types used for representing pointers. These types realize the Windows version of [I/O safety]. +//! analogous to types used for representing pointers. These types reflect the Windows version of [I/O safety]. //! //! | Type | Analogous to | //! | ---------------------- | ------------ | From 4da08115132c55289905e9304d202464bd46f059 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 14 Aug 2023 09:16:08 +0200 Subject: [PATCH 05/11] mention /proc/self/fd with /proc/self/mem --- library/std/src/os/unix/io/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/std/src/os/unix/io/mod.rs b/library/std/src/os/unix/io/mod.rs index f49aebefd20ca..c12d89ed63710 100644 --- a/library/std/src/os/unix/io/mod.rs +++ b/library/std/src/os/unix/io/mod.rs @@ -65,9 +65,9 @@ //! to be opened and read from or written must be `unsafe`. Rust's safety guarantees //! only cover what the program itself can do, and not what entities outside //! the program can do to it. `/proc/self/mem` is considered to be such an -//! external entity, along with debugging interfaces, and people with physical access to -//! the hardware. This is true even in cases where the program is controlling -//! the external entity. +//! external entity, along with `/proc/self/fd/*`, debugging interfaces, and people with physical +//! access to the hardware. This is true even in cases where the program is controlling the external +//! entity. //! //! If you desire to comprehensively prevent programs from reaching out and //! causing external entities to reach back in and violate memory safety, it's From 03c28d5626dd1e2077593fe57eff658e15958f0d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 14 Aug 2023 13:28:51 +0200 Subject: [PATCH 06/11] don't link to RFCs, they are not up-to-date docs --- library/std/src/io/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 5a9a50679574a..dd178b357dfec 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -240,7 +240,7 @@ //! //! ## I/O Safety //! -//! Rust follows an [I/O safety] discipline that is comparable to its memory safety discipline. This +//! 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 @@ -280,7 +280,6 @@ //! [`?` 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 //! [`os::unix`]: ../os/unix/io/index.html //! [`os::windows`]: ../os/windows/io/index.html //! [`OwnedFd`]: ../os/fd/struct.OwnedFd.html From 85e6e82f93aa69f57aba3362d8a94199ea4a13db Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 14 Aug 2023 13:52:21 +0200 Subject: [PATCH 07/11] reword the paragraph on file description ownership --- library/std/src/io/mod.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index dd178b357dfec..21942ef50316d 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -263,15 +263,16 @@ //! 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. +//! Note that exclusive ownership of a file descriptor does *not* imply exclusive ownership of the +//! underlying kernel object that the file descriptor references (also called "file description" on +//! some operating systems). An owned file descriptor can have duplicates, i.e., other file +//! descriptors that share the same kernel object. The exact rules around ownership of kernel +//! objects are [still unclear](https://github.com/rust-lang/rust/issues/114167). Until that is +//! clarified, the general advice is not to perform *any* operations on file descriptors that were +//! never borrowed to or owned by you. In other words, receiving a borrowed file descriptor *does* +//! give you the right to make a duplicate and use that duplicate beyond the end of the borrow, but +//! nothing gives you the right to just `write` to a file descriptor that never even got borrowed to +//! you. //! //! [`File`]: crate::fs::File //! [`TcpStream`]: crate::net::TcpStream From e9eca7cda449490fd818b853068ea21518d19066 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 14 Aug 2023 16:53:08 +0200 Subject: [PATCH 08/11] reference-counting analogy --- library/std/src/io/mod.rs | 43 +++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 21942ef50316d..3dd8ff819ac8b 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -244,35 +244,37 @@ //! 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. +//! other code is allowed to access in any way, but the owner is allowed to a access and even 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*, granting the temporary right to perform operations on this file descriptor. +//! 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 +//! To uphold I/O safety, it is crucial that no code acts on 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*. +//! acts on it, is *unsound*. //! -//! Not upholding I/O safety and closing a file descriptor without proof of ownership can lead to +//! Not upholding I/O safety and acting on 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. +//! descriptors: a closed file descriptor could be re-allocated, so the original owner of that file +//! descriptor is now working on the wrong file. Some code might even rely on fully encapsulating +//! its file descriptors with no operations being performed by any other part of the program. //! //! Note that exclusive ownership of a file descriptor does *not* imply exclusive ownership of the //! underlying kernel object that the file descriptor references (also called "file description" on -//! some operating systems). An owned file descriptor can have duplicates, i.e., other file -//! descriptors that share the same kernel object. The exact rules around ownership of kernel -//! objects are [still unclear](https://github.com/rust-lang/rust/issues/114167). Until that is -//! clarified, the general advice is not to perform *any* operations on file descriptors that were -//! never borrowed to or owned by you. In other words, receiving a borrowed file descriptor *does* -//! give you the right to make a duplicate and use that duplicate beyond the end of the borrow, but -//! nothing gives you the right to just `write` to a file descriptor that never even got borrowed to -//! you. +//! some operating systems). File descriptors basically work like [`Arc`]: when you receive an owned +//! file descriptor, you cannot know whether there are any other file descriptors that reference the +//! same kernel object. However, when you create a new kernel object, you know that you are holding +//! the only reference to it. Just be careful not to borrow it to anyone, since they can obtain a +//! clone and then you can no longer know what the reference count is! In that sense, [`OwnedFd`] is +//! like `Arc` and [`BorrowedFd<'a>`] is like `&'a Arc` (and similar for the Windows types). There +//! is no equivalent to `Box` for file descriptors in the standard library (that would be a type +//! that guarantees that the reference count is `1`). //! //! [`File`]: crate::fs::File //! [`TcpStream`]: crate::net::TcpStream @@ -284,7 +286,8 @@ //! [`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 +//! [`BorrowedFd<'a>`]: ../os/fd/struct.BorrowedFd.html +//! [`Arc`]: crate::sync::Arc #![stable(feature = "rust1", since = "1.0.0")] From 2cb9d3def2515e0717f3d8364e83dc2e52ace63c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 22 Aug 2023 08:57:38 +0200 Subject: [PATCH 09/11] typos and wording Co-authored-by: Dan Gohman --- library/std/src/io/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 3dd8ff819ac8b..4c599cbc1aa4b 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -244,9 +244,9 @@ //! 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 access in any way, but the owner is allowed to a access and even close +//! other code is allowed to access in any way, but the owner is allowed to access and even 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 +//! function. Types like [`File`] own their file descriptor. Similarly, file descriptors //! can be *borrowed*, granting the temporary right to perform operations on this file descriptor. //! 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 @@ -270,7 +270,7 @@ //! some operating systems). File descriptors basically work like [`Arc`]: when you receive an owned //! file descriptor, you cannot know whether there are any other file descriptors that reference the //! same kernel object. However, when you create a new kernel object, you know that you are holding -//! the only reference to it. Just be careful not to borrow it to anyone, since they can obtain a +//! the only reference to it. Just be careful not to lend it to anyone, since they can obtain a //! clone and then you can no longer know what the reference count is! In that sense, [`OwnedFd`] is //! like `Arc` and [`BorrowedFd<'a>`] is like `&'a Arc` (and similar for the Windows types). There //! is no equivalent to `Box` for file descriptors in the standard library (that would be a type From 6d65379e7a5da8f118803bab5618b6c68994ad37 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 22 Aug 2023 09:00:07 +0200 Subject: [PATCH 10/11] clarify what you cannot do --- library/std/src/io/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 4c599cbc1aa4b..35f5855d7f940 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -255,9 +255,9 @@ //! 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 acts on file descriptors it does not own. In -//! other words, a safe function that takes a regular integer, treats it as a file descriptor, and -//! acts on it, is *unsound*. +//! To uphold I/O safety, it is crucial that no code acts on file descriptors it does not own or +//! borrow, and 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 acts on it, is *unsound*. //! //! Not upholding I/O safety and acting on a file descriptor without proof of ownership can lead to //! misbehavior and even Undefined Behavior in code that relies on ownership of its file From 1290cd432ddc3bf935cbd1d585b5999a38a103bc Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 29 Aug 2023 21:08:46 +0200 Subject: [PATCH 11/11] further expand on Arc and Box analogy --- library/std/src/io/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 35f5855d7f940..b3cb21125c727 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -272,9 +272,12 @@ //! same kernel object. However, when you create a new kernel object, you know that you are holding //! the only reference to it. Just be careful not to lend it to anyone, since they can obtain a //! clone and then you can no longer know what the reference count is! In that sense, [`OwnedFd`] is -//! like `Arc` and [`BorrowedFd<'a>`] is like `&'a Arc` (and similar for the Windows types). There -//! is no equivalent to `Box` for file descriptors in the standard library (that would be a type -//! that guarantees that the reference count is `1`). +//! like `Arc` and [`BorrowedFd<'a>`] is like `&'a Arc` (and similar for the Windows types). In +//! particular, given a `BorrowedFd<'a>`, you are not allowed to close the file descriptor -- just +//! like how, given a `&'a Arc`, you are not allowed to decrement the reference count and +//! potentially free the underlying object. There is no equivalent to `Box` for file descriptors in +//! the standard library (that would be a type that guarantees that the reference count is `1`), +//! however, it would be possible for a crate to define a type with those semantics. //! //! [`File`]: crate::fs::File //! [`TcpStream`]: crate::net::TcpStream