xref: /llvm-project/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.remove_all/toctou.pass.cpp (revision 3497500946c9b6a1b2e1452312a24c41ee412b34)
1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // UNSUPPORTED: c++03, c++11, c++14
10 // UNSUPPORTED: no-localization
11 // UNSUPPORTED: no-threads
12 // UNSUPPORTED: no-filesystem
13 // UNSUPPORTED: availability-filesystem-missing
14 
15 // <filesystem>
16 
17 // Test for a time-of-check to time-of-use issue with std::filesystem::remove_all.
18 //
19 // Scenario:
20 // The attacker wants to get directory contents deleted, to which he does not have access.
21 // He has a way to get a privileged binary call `std::filesystem::remove_all()` on a
22 // directory he controls, e.g. in his home directory.
23 //
24 // The POC sets up the `attack_dest/attack_file` which the attacker wants to have deleted.
25 // The attacker repeatedly creates a directory and replaces it with a symlink from
26 // `victim_del` to `attack_dest` while the victim code calls `std::filesystem::remove_all()`
27 // on `victim_del`. After a few seconds the attack has succeeded and
28 // `attack_dest/attack_file` is deleted.
29 //
30 // 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
31 
32 // This test requires a dylib containing the fix shipped in https://reviews.llvm.org/D118134 (4f67a909902d).
33 // We use UNSUPPORTED instead of XFAIL because the test might not fail reliably.
34 // UNSUPPORTED: using-built-library-before-llvm-14
35 
36 // Windows doesn't support the necessary APIs to mitigate this issue.
37 // XFAIL: target={{.+}}-windows-{{.+}}
38 
39 #include <cstdio>
40 #include <filesystem>
41 #include <system_error>
42 #include <thread>
43 
44 #include <filesystem>
45 #include "filesystem_test_helper.h"
46 namespace fs = std::filesystem;
47 
main(int,char **)48 int main(int, char**) {
49   scoped_test_env env;
50   fs::path const tmpdir = env.create_dir("mydir");
51   fs::path const victim_del_path = tmpdir / "victim_del";
52   fs::path const attack_dest_dir = env.create_dir(tmpdir / "attack_dest");
53   fs::path const attack_dest_file = env.create_file(attack_dest_dir / "attack_file", 42);
54 
55   // victim just continuously removes `victim_del`
56   bool stop = false;
57   std::thread t{[&]() {
58     while (!stop) {
59         std::error_code ec;
60         fs::remove_all(victim_del_path, ec); // ignore any error
61     }
62   }};
63 
64   // attacker (could of course be in a separate process)
65   auto start_time = std::chrono::system_clock::now();
66   auto elapsed_since = [](std::chrono::system_clock::time_point const& time_point) {
67       return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - time_point);
68   };
69   bool attack_succeeded = false;
70   while (elapsed_since(start_time) < std::chrono::seconds(5)) {
71     if (!fs::exists(attack_dest_file)) {
72       std::printf("Victim deleted symlinked file outside of victim_del. Attack succeeded in %lld seconds.\n",
73                   elapsed_since(start_time).count());
74       attack_succeeded = true;
75       break;
76     }
77     std::error_code ec;
78     fs::create_directory(victim_del_path, ec);
79     if (ec) {
80       continue;
81     }
82 
83     fs::remove(victim_del_path);
84     fs::create_directory_symlink(attack_dest_dir, victim_del_path);
85   }
86   stop = true;
87   t.join();
88 
89   return attack_succeeded ? 1 : 0;
90 }
91