Skip to content

Explain TOCTOU on the top of std::fs, and reference it in functions #141847

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@
//! filesystem. All methods in this module represent cross-platform filesystem
//! operations. Extra platform-specific functionality can be found in the
//! extension traits of `std::os::$platform`.
//!
//! # Time of Check to Time of Use (TOCTOU)
//!
//! Many filesystem operations are subject to a race condition known as "Time of Check to Time of Use"
//! (TOCTOU). This occurs when a program checks a condition (like file existence or permissions)
//! and then uses the result of that check to make a decision, but the condition may have changed
//! between the check and the use.
//!
//! For example, checking if a file exists and then creating it if it doesn't is vulnerable to
//! TOCTOU - another process could create the file between your check and creation attempt.
//!
//! Another example is with symbolic links: when removing a directory, if another process replaces
//! the directory with a symbolic link between the check and the removal operation, the removal
//! might affect the wrong location. This is why operations like [`remove_dir_all`] need to use
//! atomic operations to prevent such race conditions.
//!
//! To avoid TOCTOU issues:
//! - Be aware that metadata operations (like [`metadata`] or [`symlink_metadata`]) may be affected by
//! changes made by other processes.
//! - Use atomic operations when possible (like [`File::create_new`] instead of checking existence then creating).
//! - Keep file open for the duration of operations.
Comment on lines +7 to +27
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here,

  1. first explain what is TOCTOU
  2. present two examples including create and remove_dir_all, from simple to complex.
  3. give three notes to try to avoid TOCTOU.


#![stable(feature = "rust1", since = "1.0.0")]
#![deny(unsafe_op_in_unsafe_fn)]
Expand Down Expand Up @@ -549,13 +570,14 @@ impl File {
/// non-exhaustive list of likely errors.
///
/// This option is useful because it is atomic. Otherwise between checking whether a file
/// exists and creating a new one, the file may have been created by another process (a TOCTOU
/// exists and creating a new one, the file may have been created by another process (a [TOCTOU]
/// race condition / attack).
///
/// This can also be written using
/// `File::options().read(true).write(true).create_new(true).open(...)`.
///
/// [`AlreadyExists`]: crate::io::ErrorKind::AlreadyExists
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
///
/// # Examples
///
Expand Down Expand Up @@ -1580,7 +1602,7 @@ impl OpenOptions {
///
/// This option is useful because it is atomic. Otherwise between checking
/// whether a file exists and creating a new one, the file may have been
/// created by another process (a TOCTOU race condition / attack).
/// created by another process (a [TOCTOU] race condition / attack).
///
/// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are
/// ignored.
Expand All @@ -1591,6 +1613,7 @@ impl OpenOptions {
/// [`.create()`]: OpenOptions::create
/// [`.truncate()`]: OpenOptions::truncate
/// [`AlreadyExists`]: io::ErrorKind::AlreadyExists
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
///
/// # Examples
///
Expand Down Expand Up @@ -2924,17 +2947,17 @@ pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
/// `GetFileInformationByHandleEx`, `SetFileInformationByHandle`, and `NtCreateFile`.
///
/// ## Time-of-check to time-of-use (TOCTOU) race conditions
/// On a few platforms there is no way to remove a directory's contents without following symlinks
/// unless you perform a check and then operate on paths based on that directory.
/// This allows concurrently-running code to replace the directory with a symlink after the check,
/// causing a removal to instead operate on a path based on the symlink. This is a TOCTOU race.
/// By default, `fs::remove_dir_all` protects against a symlink TOCTOU race on all platforms
/// except the following. It should not be used in security-sensitive contexts on these platforms:
/// - Miri: Even when emulating targets where the underlying implementation will protect against
/// TOCTOU races, Miri will not do so.
/// - Redox OS: This function does not protect against TOCTOU races, as Redox does not implement
/// the required platform support to do so.
/// See the [module-level TOCTOU explanation](self#time-of-check-to-time-of-use-toctou).
///
/// On most platforms, `fs::remove_dir_all` protects against symlink TOCTOU races by default.
/// However, on the following platforms, this protection is not provided and the function should
/// not be used in security-sensitive contexts:
/// - **Miri**: Even when emulating targets where the underlying implementation will protect against
/// TOCTOU races, Miri will not do so.
/// - **Redox OS**: This function does not protect against TOCTOU races, as Redox does not implement
/// the required platform support to do so.
Comment on lines +2950 to +2958
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored the contents in #141832. I moved the synbolic TOCTOU race example to the top of fs.rs, and only left which platforms should be taken care of.

///
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
/// [changes]: io#platform-specific-behavior
///
/// # Errors
Expand Down Expand Up @@ -3208,7 +3231,7 @@ impl AsInnerMut<fs_imp::DirBuilder> for DirBuilder {
/// permission is denied on one of the parent directories.
///
/// Note that while this avoids some pitfalls of the `exists()` method, it still can not
/// prevent time-of-check to time-of-use (TOCTOU) bugs. You should only use it in scenarios
/// prevent time-of-check to time-of-use ([TOCTOU]) bugs. You should only use it in scenarios
/// where those bugs are not an issue.
///
/// # Examples
Expand All @@ -3221,6 +3244,7 @@ impl AsInnerMut<fs_imp::DirBuilder> for DirBuilder {
/// ```
///
/// [`Path::exists`]: crate::path::Path::exists
/// [TOCTOU]: self#time-of-check-to-time-of-use-toctou
#[stable(feature = "fs_try_exists", since = "1.81.0")]
#[inline]
pub fn exists<P: AsRef<Path>>(path: P) -> io::Result<bool> {
Expand Down
6 changes: 4 additions & 2 deletions library/std/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3090,7 +3090,7 @@ impl Path {
/// Returns `true` if the path points at an existing entity.
///
/// Warning: this method may be error-prone, consider using [`try_exists()`] instead!
/// It also has a risk of introducing time-of-check to time-of-use (TOCTOU) bugs.
/// It also has a risk of introducing time-of-check to time-of-use ([TOCTOU]) bugs.
///
/// This function will traverse symbolic links to query information about the
/// destination file.
Expand All @@ -3111,6 +3111,7 @@ impl Path {
/// check errors, call [`Path::try_exists`].
///
/// [`try_exists()`]: Self::try_exists
/// [TOCTOU]: fs#time-of-check-to-time-of-use-toctou
#[stable(feature = "path_ext", since = "1.5.0")]
#[must_use]
#[inline]
Expand All @@ -3130,7 +3131,7 @@ impl Path {
/// permission is denied on one of the parent directories.
///
/// Note that while this avoids some pitfalls of the `exists()` method, it still can not
/// prevent time-of-check to time-of-use (TOCTOU) bugs. You should only use it in scenarios
/// prevent time-of-check to time-of-use ([TOCTOU]) bugs. You should only use it in scenarios
/// where those bugs are not an issue.
///
/// This is an alias for [`std::fs::exists`](crate::fs::exists).
Expand All @@ -3143,6 +3144,7 @@ impl Path {
/// assert!(Path::new("/root/secret_file.txt").try_exists().is_err());
/// ```
///
/// [TOCTOU]: fs#time-of-check-to-time-of-use-toctou
/// [`exists()`]: Self::exists
#[stable(feature = "path_try_exists", since = "1.63.0")]
#[inline]
Expand Down
Loading