Lucene search

K
hackeroneHkratzH1:1520931
HistoryMar 24, 2022 - 9:23 a.m.

Internet Bug Bounty: Time-of-check to time-of-use vulnerability in the std::fs::remove_dir_all() function of the Rust standard library

2022-03-2409:23:58
hkratz
hackerone.com
$4000
53

6.3 Medium

CVSS3

Attack Vector

LOCAL

Attack Complexity

HIGH

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H

3.3 Low

CVSS2

Access Vector

LOCAL

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:L/AC:M/Au:N/C:N/I:P/A:P

0.0005 Low

EPSS

Percentile

14.8%

The implementation of std::fs::remove_dir_all() in the Rust standard library is vulnerable to a time-of-check to time-of-use link replacement attack. This applies to all versions of Rust before 1.58.1.

Vulnerability details

The documentation of std::fs::remove_dir_all() guarantees that the function does not follow symbolic links:
> Removes a directory at this path, after removing all its contents. Use carefully!
> This function does not follow symbolic links and it will simply remove the symbolic link itself.

The vulnerable implementation for Windows is in library/std/src/sys/windows/fs.rs. For other platforms it is in library/std/src/sys_common/fs.rs. Both use a remove_dir_all_recursive() helper function which does the actual recursion and deletion. It opens directory by the given path and iterates the directory entries. For each directory entry it checks if the entry is a directory and recurses into it if it is. If it is not it is deleted using std::fs::remove_file(). On the way back up the now empty directories are deleted using std::fs::remove_dir()

There are two problems with this implementation if the attacker has write access to a directory which is being deleted by the privileged process:

  1. The type of a directory entry is checked and it is being recursed into if it is a directory. There is a short time window between the check and the opening of the subdirectory which an attacker can exploit by replacing the subdirectory with symlink causing the symlink to be followed.

  2. The path given to std::fs::remove_dir_all() is extended with subentry paths which are then used to process subdirectories and delete directory entries. Paths are resolved by the operating system each time they are passed to a system call. If the attacker can replace a descendent directory of the directory passed to remove_dir_all() while a subdirectory of it is being processed with a symlink he can cause that symlink to be followed in subsequent filesystem operations.

A proof-of-concept code demonstrating the vulnerability is attached.

Mitigation

  • Update to Rust 1.58.1 or later which includes a fixed implementation for all supported platforms except for macOS before version 10.10 and REDOX.
  • Don’t use the vulnerable std::fs::remove_dir_all() in a privileged process or any other security-senstitive context.
  • Make sure that std::fs::remove_dir_all() is only used on directories not accessible to processes from other security contexts.

Impact

If the attacker has write access to a directory which is being deleted by the privileged process using remove_dir_all() he can trick the process to delete any sensitive files or directory subtrees that the privileged process can.

6.3 Medium

CVSS3

Attack Vector

LOCAL

Attack Complexity

HIGH

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H

3.3 Low

CVSS2

Access Vector

LOCAL

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:L/AC:M/Au:N/C:N/I:P/A:P

0.0005 Low

EPSS

Percentile

14.8%