os_validation_win_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <windows.h>
  5. #include <shlobj.h>
  6. #include <iterator>
  7. #include <memory>
  8. #include <string>
  9. #include <tuple>
  10. #include "base/callback_helpers.h"
  11. #include "base/files/file_path.h"
  12. #include "base/files/file_util.h"
  13. #include "base/files/scoped_temp_dir.h"
  14. #include "base/memory/ptr_util.h"
  15. #include "base/strings/string_util.h"
  16. #include "base/win/scoped_handle.h"
  17. #include "testing/gtest/include/gtest/gtest.h"
  18. #define FPL FILE_PATH_LITERAL
  19. namespace base {
  20. // A basic test harness that creates a temporary directory during test case
  21. // setup and deletes it during teardown.
  22. class OsValidationTest : public ::testing::Test {
  23. protected:
  24. // ::testing::Test:
  25. static void SetUpTestCase() {
  26. temp_dir_ = std::make_unique<ScopedTempDir>().release();
  27. ASSERT_TRUE(temp_dir_->CreateUniqueTempDir());
  28. }
  29. static void TearDownTestCase() {
  30. // Explicitly delete the dir to catch any deletion errors.
  31. ASSERT_TRUE(temp_dir_->Delete());
  32. auto temp_dir = base::WrapUnique(temp_dir_);
  33. temp_dir_ = nullptr;
  34. }
  35. // Returns the path to the test's temporary directory.
  36. static const FilePath& temp_path() { return temp_dir_->GetPath(); }
  37. private:
  38. static ScopedTempDir* temp_dir_;
  39. };
  40. // static
  41. ScopedTempDir* OsValidationTest::temp_dir_ = nullptr;
  42. // A test harness for exhaustively evaluating the conditions under which an open
  43. // file may be operated on. Template parameters are used to turn off or on
  44. // various bits in the access rights and sharing mode bitfields. These template
  45. // parameters are:
  46. // - The standard access right bits (except for WRITE_OWNER, which requires
  47. // admin rights): SYNCHRONIZE, WRITE_DAC, READ_CONTROL, DELETE.
  48. // - Generic file access rights: FILE_GENERIC_READ, FILE_GENERIC_WRITE,
  49. // FILE_EXECUTE.
  50. // - The sharing bits: FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE.
  51. class OpenFileTest : public OsValidationTest,
  52. public ::testing::WithParamInterface<
  53. std::tuple<std::tuple<DWORD, DWORD, DWORD, DWORD>,
  54. std::tuple<DWORD, DWORD, DWORD>,
  55. std::tuple<DWORD, DWORD, DWORD>>> {
  56. protected:
  57. OpenFileTest() = default;
  58. OpenFileTest(const OpenFileTest&) = delete;
  59. OpenFileTest& operator=(const OpenFileTest&) = delete;
  60. // Returns a dwDesiredAccess bitmask for use with CreateFileW containing the
  61. // test's access right bits.
  62. static DWORD GetAccess() {
  63. // Extract the two tuples of standard and generic file rights.
  64. std::tuple<DWORD, DWORD, DWORD, DWORD> standard_rights;
  65. std::tuple<DWORD, DWORD, DWORD> generic_rights;
  66. std::tie(standard_rights, generic_rights, std::ignore) = GetParam();
  67. // Extract the five standard rights bits.
  68. auto [synchronize_bit, write_dac_bit, read_control_bit, delete_bit] =
  69. standard_rights;
  70. // Extract the three generic file rights masks.
  71. auto [file_generic_read_bits, file_generic_write_bits,
  72. file_generic_execute_bits] = generic_rights;
  73. // Combine and return the desired access rights.
  74. return synchronize_bit | write_dac_bit | read_control_bit | delete_bit |
  75. file_generic_read_bits | file_generic_write_bits |
  76. file_generic_execute_bits;
  77. }
  78. // Returns a dwShareMode bitmask for use with CreateFileW containing the
  79. // tests's share mode bits.
  80. static DWORD GetShareMode() {
  81. // Extract the tuple of sharing mode bits.
  82. std::tuple<DWORD, DWORD, DWORD> sharing_bits;
  83. std::tie(std::ignore, std::ignore, sharing_bits) = GetParam();
  84. // Extract the sharing mode bits.
  85. auto [share_read_bit, share_write_bit, share_delete_bit] = sharing_bits;
  86. // Combine and return the sharing mode.
  87. return share_read_bit | share_write_bit | share_delete_bit;
  88. }
  89. // Appends string representation of the access rights bits present in |access|
  90. // to |result|.
  91. static void AppendAccessString(DWORD access, std::string* result) {
  92. #define ENTRY(a) \
  93. { a, #a }
  94. static constexpr BitAndName kBitNames[] = {
  95. // The standard access rights:
  96. ENTRY(SYNCHRONIZE),
  97. ENTRY(WRITE_OWNER),
  98. ENTRY(WRITE_DAC),
  99. ENTRY(READ_CONTROL),
  100. ENTRY(DELETE),
  101. // The file-specific access rights:
  102. ENTRY(FILE_WRITE_ATTRIBUTES),
  103. ENTRY(FILE_READ_ATTRIBUTES),
  104. ENTRY(FILE_EXECUTE),
  105. ENTRY(FILE_WRITE_EA),
  106. ENTRY(FILE_READ_EA),
  107. ENTRY(FILE_APPEND_DATA),
  108. ENTRY(FILE_WRITE_DATA),
  109. ENTRY(FILE_READ_DATA),
  110. };
  111. #undef ENTRY
  112. ASSERT_NO_FATAL_FAILURE(AppendBitsToString(access, std::begin(kBitNames),
  113. std::end(kBitNames), result));
  114. }
  115. // Appends a string representation of the sharing mode bits present in
  116. // |share_mode| to |result|.
  117. static void AppendShareModeString(DWORD share_mode, std::string* result) {
  118. #define ENTRY(a) \
  119. { a, #a }
  120. static constexpr BitAndName kBitNames[] = {
  121. ENTRY(FILE_SHARE_DELETE),
  122. ENTRY(FILE_SHARE_WRITE),
  123. ENTRY(FILE_SHARE_READ),
  124. };
  125. #undef ENTRY
  126. ASSERT_NO_FATAL_FAILURE(AppendBitsToString(
  127. share_mode, std::begin(kBitNames), std::end(kBitNames), result));
  128. }
  129. // Returns true if we expect that a file opened with |access| access rights
  130. // and |share_mode| sharing can be moved via MoveFileEx, and can be deleted
  131. // via DeleteFile so long as it is not mapped into a process.
  132. static bool CanMoveFile(DWORD access, DWORD share_mode) {
  133. // A file can be moved as long as it is opened with FILE_SHARE_DELETE or
  134. // if nothing beyond the standard access rights (save DELETE) has been
  135. // requested. It can be deleted under those same circumstances as long as
  136. // it has not been mapped into a process.
  137. constexpr DWORD kStandardNoDelete = STANDARD_RIGHTS_ALL & ~DELETE;
  138. return ((share_mode & FILE_SHARE_DELETE) != 0) ||
  139. ((access & ~kStandardNoDelete) == 0);
  140. }
  141. // OsValidationTest:
  142. void SetUp() override {
  143. OsValidationTest::SetUp();
  144. // Determine the desired access and share mode for this test.
  145. access_ = GetAccess();
  146. share_mode_ = GetShareMode();
  147. // Make a ScopedTrace instance for comprehensible output.
  148. std::string access_string;
  149. ASSERT_NO_FATAL_FAILURE(AppendAccessString(access_, &access_string));
  150. std::string share_mode_string;
  151. ASSERT_NO_FATAL_FAILURE(
  152. AppendShareModeString(share_mode_, &share_mode_string));
  153. scoped_trace_ = std::make_unique<::testing::ScopedTrace>(
  154. __FILE__, __LINE__, access_string + ", " + share_mode_string);
  155. // Make a copy of imm32.dll in the temp dir for fiddling.
  156. ASSERT_TRUE(CreateTemporaryFileInDir(temp_path(), &temp_file_path_));
  157. ASSERT_TRUE(CopyFile(FilePath(FPL("c:\\windows\\system32\\imm32.dll")),
  158. temp_file_path_));
  159. // Open the file
  160. file_handle_.Set(::CreateFileW(temp_file_path_.value().c_str(), access_,
  161. share_mode_, nullptr, OPEN_EXISTING,
  162. FILE_ATTRIBUTE_NORMAL, nullptr));
  163. ASSERT_TRUE(file_handle_.is_valid()) << ::GetLastError();
  164. // Get a second unique name in the temp dir to which the file might be
  165. // moved.
  166. temp_file_dest_path_ = temp_file_path_.InsertBeforeExtension(FPL("bla"));
  167. }
  168. void TearDown() override {
  169. file_handle_.Close();
  170. // Manually delete the temp files since the temp dir is reused across tests.
  171. ASSERT_TRUE(DeleteFile(temp_file_path_));
  172. ASSERT_TRUE(DeleteFile(temp_file_dest_path_));
  173. }
  174. DWORD access() const { return access_; }
  175. DWORD share_mode() const { return share_mode_; }
  176. const FilePath& temp_file_path() const { return temp_file_path_; }
  177. const FilePath& temp_file_dest_path() const { return temp_file_dest_path_; }
  178. HANDLE file_handle() const { return file_handle_.get(); }
  179. private:
  180. struct BitAndName {
  181. DWORD bit;
  182. StringPiece name;
  183. };
  184. // Appends the names of the bits present in |bitfield| to |result| based on
  185. // the array of bit-to-name mappings bounded by |bits_begin| and |bits_end|.
  186. static void AppendBitsToString(DWORD bitfield,
  187. const BitAndName* bits_begin,
  188. const BitAndName* bits_end,
  189. std::string* result) {
  190. while (bits_begin < bits_end) {
  191. const BitAndName& bit_name = *bits_begin;
  192. if (bitfield & bit_name.bit) {
  193. if (!result->empty())
  194. result->append(" | ");
  195. result->append(bit_name.name.data(), bit_name.name.size());
  196. bitfield &= ~bit_name.bit;
  197. }
  198. ++bits_begin;
  199. }
  200. ASSERT_EQ(bitfield, DWORD{0});
  201. }
  202. DWORD access_ = 0;
  203. DWORD share_mode_ = 0;
  204. std::unique_ptr<::testing::ScopedTrace> scoped_trace_;
  205. FilePath temp_file_path_;
  206. FilePath temp_file_dest_path_;
  207. win::ScopedHandle file_handle_;
  208. };
  209. // Tests that an opened but not mapped file can be deleted as expected.
  210. TEST_P(OpenFileTest, DeleteFile) {
  211. if (CanMoveFile(access(), share_mode())) {
  212. EXPECT_NE(::DeleteFileW(temp_file_path().value().c_str()), 0)
  213. << "Last error code: " << ::GetLastError();
  214. } else {
  215. EXPECT_EQ(::DeleteFileW(temp_file_path().value().c_str()), 0);
  216. }
  217. }
  218. // Tests that an opened file can be moved as expected.
  219. TEST_P(OpenFileTest, MoveFileEx) {
  220. if (CanMoveFile(access(), share_mode())) {
  221. EXPECT_NE(::MoveFileExW(temp_file_path().value().c_str(),
  222. temp_file_dest_path().value().c_str(), 0),
  223. 0)
  224. << "Last error code: " << ::GetLastError();
  225. } else {
  226. EXPECT_EQ(::MoveFileExW(temp_file_path().value().c_str(),
  227. temp_file_dest_path().value().c_str(), 0),
  228. 0);
  229. }
  230. }
  231. // Tests that an open file cannot be moved after it has been marked for
  232. // deletion.
  233. TEST_P(OpenFileTest, DeleteThenMove) {
  234. // Don't test combinations that cannot be deleted.
  235. if (!CanMoveFile(access(), share_mode()))
  236. return;
  237. ASSERT_NE(::DeleteFileW(temp_file_path().value().c_str()), 0)
  238. << "Last error code: " << ::GetLastError();
  239. // Move fails with ERROR_ACCESS_DENIED (STATUS_DELETE_PENDING under the
  240. // covers).
  241. EXPECT_EQ(::MoveFileExW(temp_file_path().value().c_str(),
  242. temp_file_dest_path().value().c_str(), 0),
  243. 0);
  244. }
  245. // Tests that an open file that is mapped into memory can be moved but not
  246. // deleted.
  247. TEST_P(OpenFileTest, MapThenDelete) {
  248. // There is nothing to test if the file can't be read.
  249. if (!(access() & FILE_READ_DATA))
  250. return;
  251. // Pick the protection option that matches the access rights used to open the
  252. // file.
  253. static constexpr struct {
  254. DWORD access_bits;
  255. DWORD protection;
  256. } kAccessToProtection[] = {
  257. // Sorted from most- to least-bits used for logic below.
  258. {FILE_READ_DATA | FILE_WRITE_DATA | FILE_EXECUTE, PAGE_EXECUTE_READWRITE},
  259. {FILE_READ_DATA | FILE_WRITE_DATA, PAGE_READWRITE},
  260. {FILE_READ_DATA | FILE_EXECUTE, PAGE_EXECUTE_READ},
  261. {FILE_READ_DATA, PAGE_READONLY},
  262. };
  263. DWORD protection = 0;
  264. for (const auto& scan : kAccessToProtection) {
  265. if ((access() & scan.access_bits) == scan.access_bits) {
  266. protection = scan.protection;
  267. break;
  268. }
  269. }
  270. ASSERT_NE(protection, DWORD{0});
  271. win::ScopedHandle mapping(::CreateFileMappingA(
  272. file_handle(), nullptr, protection | SEC_IMAGE, 0, 0, nullptr));
  273. auto result = ::GetLastError();
  274. ASSERT_TRUE(mapping.is_valid()) << result;
  275. auto* view = ::MapViewOfFile(mapping.get(), FILE_MAP_READ, 0, 0, 0);
  276. result = ::GetLastError();
  277. ASSERT_NE(view, nullptr) << result;
  278. ScopedClosureRunner unmapper(
  279. BindOnce([](const void* view) { ::UnmapViewOfFile(view); }, view));
  280. // Mapped files cannot be deleted under any circumstances.
  281. EXPECT_EQ(::DeleteFileW(temp_file_path().value().c_str()), 0);
  282. // But can still be moved under the same conditions as if it weren't mapped.
  283. if (CanMoveFile(access(), share_mode())) {
  284. EXPECT_NE(::MoveFileExW(temp_file_path().value().c_str(),
  285. temp_file_dest_path().value().c_str(), 0),
  286. 0)
  287. << "Last error code: " << ::GetLastError();
  288. } else {
  289. EXPECT_EQ(::MoveFileExW(temp_file_path().value().c_str(),
  290. temp_file_dest_path().value().c_str(), 0),
  291. 0);
  292. }
  293. }
  294. // These tests are intentionally disabled by default. They were created as an
  295. // educational tool to understand the restrictions on moving and deleting files
  296. // on Windows. There is every expectation that once they pass, they will always
  297. // pass. It might be interesting to run them manually on new versions of the OS,
  298. // but there is no need to run them on every try/CQ run. Here is one possible
  299. // way to run them all locally:
  300. //
  301. // base_unittests.exe --single-process-tests --gtest_also_run_disabled_tests \
  302. // --gtest_filter=*OpenFileTest*
  303. INSTANTIATE_TEST_SUITE_P(
  304. DISABLED_Test,
  305. OpenFileTest,
  306. ::testing::Combine(
  307. // Standard access rights except for WRITE_OWNER, which requires admin.
  308. ::testing::Combine(::testing::Values(0, SYNCHRONIZE),
  309. ::testing::Values(0, WRITE_DAC),
  310. ::testing::Values(0, READ_CONTROL),
  311. ::testing::Values(0, DELETE)),
  312. // Generic file access rights.
  313. ::testing::Combine(::testing::Values(0, FILE_GENERIC_READ),
  314. ::testing::Values(0, FILE_GENERIC_WRITE),
  315. ::testing::Values(0, FILE_GENERIC_EXECUTE)),
  316. // File sharing mode.
  317. ::testing::Combine(::testing::Values(0, FILE_SHARE_READ),
  318. ::testing::Values(0, FILE_SHARE_WRITE),
  319. ::testing::Values(0, FILE_SHARE_DELETE))));
  320. } // namespace base