Skip to content

Commit c43faef

Browse files
authored
Rollup merge of rust-lang#141847 - xizheyin:141837, r=jhpratt
Explain `TOCTOU` on the top of `std::fs`, and reference it in functions Fixes rust-lang#141837 r? `@workingjubilee`
2 parents b0a9446 + e95751a commit c43faef

File tree

2 files changed

+41
-15
lines changed

2 files changed

+41
-15
lines changed

library/std/src/fs.rs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@
44
//! filesystem. All methods in this module represent cross-platform filesystem
55
//! operations. Extra platform-specific functionality can be found in the
66
//! extension traits of `std::os::$platform`.
7+
//!
8+
//! # Time of Check to Time of Use (TOCTOU)
9+
//!
10+
//! Many filesystem operations are subject to a race condition known as "Time of Check to Time of Use"
11+
//! (TOCTOU). This occurs when a program checks a condition (like file existence or permissions)
12+
//! and then uses the result of that check to make a decision, but the condition may have changed
13+
//! between the check and the use.
14+
//!
15+
//! For example, checking if a file exists and then creating it if it doesn't is vulnerable to
16+
//! TOCTOU - another process could create the file between your check and creation attempt.
17+
//!
18+
//! Another example is with symbolic links: when removing a directory, if another process replaces
19+
//! the directory with a symbolic link between the check and the removal operation, the removal
20+
//! might affect the wrong location. This is why operations like [`remove_dir_all`] need to use
21+
//! atomic operations to prevent such race conditions.
22+
//!
23+
//! To avoid TOCTOU issues:
24+
//! - Be aware that metadata operations (like [`metadata`] or [`symlink_metadata`]) may be affected by
25+
//! changes made by other processes.
26+
//! - Use atomic operations when possible (like [`File::create_new`] instead of checking existence then creating).
27+
//! - Keep file open for the duration of operations.
728
829
#![stable(feature = "rust1", since = "1.0.0")]
930
#![deny(unsafe_op_in_unsafe_fn)]
@@ -548,13 +569,14 @@ impl File {
548569
/// non-exhaustive list of likely errors.
549570
///
550571
/// This option is useful because it is atomic. Otherwise between checking whether a file
551-
/// exists and creating a new one, the file may have been created by another process (a TOCTOU
572+
/// exists and creating a new one, the file may have been created by another process (a [TOCTOU]
552573
/// race condition / attack).
553574
///
554575
/// This can also be written using
555576
/// `File::options().read(true).write(true).create_new(true).open(...)`.
556577
///
557578
/// [`AlreadyExists`]: crate::io::ErrorKind::AlreadyExists
579+
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
558580
///
559581
/// # Examples
560582
///
@@ -1610,7 +1632,7 @@ impl OpenOptions {
16101632
///
16111633
/// This option is useful because it is atomic. Otherwise between checking
16121634
/// whether a file exists and creating a new one, the file may have been
1613-
/// created by another process (a TOCTOU race condition / attack).
1635+
/// created by another process (a [TOCTOU] race condition / attack).
16141636
///
16151637
/// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are
16161638
/// ignored.
@@ -1621,6 +1643,7 @@ impl OpenOptions {
16211643
/// [`.create()`]: OpenOptions::create
16221644
/// [`.truncate()`]: OpenOptions::truncate
16231645
/// [`AlreadyExists`]: io::ErrorKind::AlreadyExists
1646+
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
16241647
///
16251648
/// # Examples
16261649
///
@@ -2954,17 +2977,17 @@ pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
29542977
/// `GetFileInformationByHandleEx`, `SetFileInformationByHandle`, and `NtCreateFile`.
29552978
///
29562979
/// ## Time-of-check to time-of-use (TOCTOU) race conditions
2957-
/// On a few platforms there is no way to remove a directory's contents without following symlinks
2958-
/// unless you perform a check and then operate on paths based on that directory.
2959-
/// This allows concurrently-running code to replace the directory with a symlink after the check,
2960-
/// causing a removal to instead operate on a path based on the symlink. This is a TOCTOU race.
2961-
/// By default, `fs::remove_dir_all` protects against a symlink TOCTOU race on all platforms
2962-
/// except the following. It should not be used in security-sensitive contexts on these platforms:
2963-
/// - Miri: Even when emulating targets where the underlying implementation will protect against
2964-
/// TOCTOU races, Miri will not do so.
2965-
/// - Redox OS: This function does not protect against TOCTOU races, as Redox does not implement
2966-
/// the required platform support to do so.
2980+
/// See the [module-level TOCTOU explanation](self#time-of-check-to-time-of-use-toctou).
2981+
///
2982+
/// On most platforms, `fs::remove_dir_all` protects against symlink TOCTOU races by default.
2983+
/// However, on the following platforms, this protection is not provided and the function should
2984+
/// not be used in security-sensitive contexts:
2985+
/// - **Miri**: Even when emulating targets where the underlying implementation will protect against
2986+
/// TOCTOU races, Miri will not do so.
2987+
/// - **Redox OS**: This function does not protect against TOCTOU races, as Redox does not implement
2988+
/// the required platform support to do so.
29672989
///
2990+
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
29682991
/// [changes]: io#platform-specific-behavior
29692992
///
29702993
/// # Errors
@@ -3238,7 +3261,7 @@ impl AsInnerMut<fs_imp::DirBuilder> for DirBuilder {
32383261
/// permission is denied on one of the parent directories.
32393262
///
32403263
/// Note that while this avoids some pitfalls of the `exists()` method, it still can not
3241-
/// prevent time-of-check to time-of-use (TOCTOU) bugs. You should only use it in scenarios
3264+
/// prevent time-of-check to time-of-use ([TOCTOU]) bugs. You should only use it in scenarios
32423265
/// where those bugs are not an issue.
32433266
///
32443267
/// # Examples
@@ -3251,6 +3274,7 @@ impl AsInnerMut<fs_imp::DirBuilder> for DirBuilder {
32513274
/// ```
32523275
///
32533276
/// [`Path::exists`]: crate::path::Path::exists
3277+
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
32543278
#[stable(feature = "fs_try_exists", since = "1.81.0")]
32553279
#[inline]
32563280
pub fn exists<P: AsRef<Path>>(path: P) -> io::Result<bool> {

library/std/src/path.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3127,7 +3127,7 @@ impl Path {
31273127
/// Returns `true` if the path points at an existing entity.
31283128
///
31293129
/// Warning: this method may be error-prone, consider using [`try_exists()`] instead!
3130-
/// It also has a risk of introducing time-of-check to time-of-use (TOCTOU) bugs.
3130+
/// It also has a risk of introducing time-of-check to time-of-use ([TOCTOU]) bugs.
31313131
///
31323132
/// This function will traverse symbolic links to query information about the
31333133
/// destination file.
@@ -3148,6 +3148,7 @@ impl Path {
31483148
/// check errors, call [`Path::try_exists`].
31493149
///
31503150
/// [`try_exists()`]: Self::try_exists
3151+
/// [TOCTOU]: fs#time-of-check-to-time-of-use-toctou
31513152
#[stable(feature = "path_ext", since = "1.5.0")]
31523153
#[must_use]
31533154
#[inline]
@@ -3167,7 +3168,7 @@ impl Path {
31673168
/// permission is denied on one of the parent directories.
31683169
///
31693170
/// Note that while this avoids some pitfalls of the `exists()` method, it still can not
3170-
/// prevent time-of-check to time-of-use (TOCTOU) bugs. You should only use it in scenarios
3171+
/// prevent time-of-check to time-of-use ([TOCTOU]) bugs. You should only use it in scenarios
31713172
/// where those bugs are not an issue.
31723173
///
31733174
/// This is an alias for [`std::fs::exists`](crate::fs::exists).
@@ -3180,6 +3181,7 @@ impl Path {
31803181
/// assert!(Path::new("/root/secret_file.txt").try_exists().is_err());
31813182
/// ```
31823183
///
3184+
/// [TOCTOU]: fs#time-of-check-to-time-of-use-toctou
31833185
/// [`exists()`]: Self::exists
31843186
#[stable(feature = "path_try_exists", since = "1.63.0")]
31853187
#[inline]

0 commit comments

Comments
 (0)