OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Crashpad Authors. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 #include "util/win/safe_terminate_process.h" |
| 16 |
| 17 #include <string.h> |
| 18 |
| 19 #include <string> |
| 20 #include <memory> |
| 21 |
| 22 #include "base/files/file_path.h" |
| 23 #include "base/logging.h" |
| 24 #include "base/macros.h" |
| 25 #include "build/build_config.h" |
| 26 #include "gtest/gtest.h" |
| 27 #include "test/errors.h" |
| 28 #include "test/test_paths.h" |
| 29 #include "test/win/child_launcher.h" |
| 30 #include "util/win/scoped_handle.h" |
| 31 |
| 32 namespace crashpad { |
| 33 namespace test { |
| 34 namespace { |
| 35 |
| 36 // Patches executable code, saving a copy of the original code so that it can be |
| 37 // restored on destruction. |
| 38 class ScopedExecutablePatch { |
| 39 public: |
| 40 ScopedExecutablePatch(void* target, const void* source, size_t size) |
| 41 : original_(new uint8_t[size]), target_(target), size_(size) { |
| 42 memcpy(original_.get(), target_, size_); |
| 43 |
| 44 ScopedVirtualProtectRWX protect_rwx(target_, size_); |
| 45 memcpy(target_, source, size_); |
| 46 } |
| 47 |
| 48 ~ScopedExecutablePatch() { |
| 49 ScopedVirtualProtectRWX protect_rwx(target_, size_); |
| 50 memcpy(target_, original_.get(), size_); |
| 51 } |
| 52 |
| 53 private: |
| 54 // Sets the protection on (address, size) to PAGE_EXECUTE_READWRITE by calling |
| 55 // VirtualProtect(), and restores the original protection on destruction. Note |
| 56 // that the region may span multiple pages, but the first page’s original |
| 57 // protection will be applied to the entire region on destruction. This |
| 58 // shouldn’t be a problem in practice for patching a function for this test’s |
| 59 // purposes. |
| 60 class ScopedVirtualProtectRWX { |
| 61 public: |
| 62 // If either the constructor or destructor fails, PCHECK() to terminate |
| 63 // immediately, because the process will be in a weird and untrustworthy |
| 64 // state, and gtest error handling isn’t worthwhile at that point. |
| 65 |
| 66 ScopedVirtualProtectRWX(void* address, size_t size) |
| 67 : address_(address), size_(size) { |
| 68 PCHECK(VirtualProtect( |
| 69 address_, size_, PAGE_EXECUTE_READWRITE, &old_protect_)) |
| 70 << "VirtualProtect"; |
| 71 } |
| 72 |
| 73 ~ScopedVirtualProtectRWX() { |
| 74 DWORD last_protect_; |
| 75 PCHECK(VirtualProtect(address_, size_, old_protect_, &last_protect_)) |
| 76 << "VirtualProtect"; |
| 77 } |
| 78 |
| 79 private: |
| 80 void* address_; |
| 81 size_t size_; |
| 82 DWORD old_protect_; |
| 83 |
| 84 DISALLOW_COPY_AND_ASSIGN(ScopedVirtualProtectRWX); |
| 85 }; |
| 86 |
| 87 std::unique_ptr<uint8_t[]> original_; |
| 88 void* target_; |
| 89 size_t size_; |
| 90 |
| 91 DISALLOW_COPY_AND_ASSIGN(ScopedExecutablePatch); |
| 92 }; |
| 93 |
| 94 TEST(SafeTerminateProcess, PatchBadly) { |
| 95 // This is a test of SafeTerminateProcess(), but it doesn’t actually terminate |
| 96 // anything. Instead, it works with a process handle for the current process |
| 97 // that doesn’t have PROCESS_TERMINATE access. The whole point of this test is |
| 98 // to patch the real TerminateProcess() badly with a cdecl implementation to |
| 99 // ensure that SafeTerminateProcess() can recover from such gross misconduct. |
| 100 // The actual termination isn’t relevant to this test. |
| 101 // |
| 102 // Notably, don’t duplicate the process handle with PROCESS_TERMINATE access |
| 103 // or with the DUPLICATE_SAME_ACCESS option. The SafeTerminateProcess() calls |
| 104 // that follow operate on a duplicate of the current process’ process handle, |
| 105 // and they’re supposed to fail, not terminate this process. |
| 106 HANDLE process; |
| 107 ASSERT_TRUE(DuplicateHandle(GetCurrentProcess(), |
| 108 GetCurrentProcess(), |
| 109 GetCurrentProcess(), |
| 110 &process, |
| 111 PROCESS_QUERY_INFORMATION, |
| 112 false, |
| 113 0)) |
| 114 << ErrorMessage("DuplicateHandle"); |
| 115 ScopedKernelHANDLE process_owner(process); |
| 116 |
| 117 // Make sure that TerminateProcess() works as a baseline. |
| 118 SetLastError(ERROR_SUCCESS); |
| 119 EXPECT_FALSE(TerminateProcess(process, 0)); |
| 120 EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); |
| 121 |
| 122 // Make sure that SafeTerminateProcess() works, calling through to |
| 123 // TerminateProcess() properly. |
| 124 SetLastError(ERROR_SUCCESS); |
| 125 EXPECT_FALSE(SafeTerminateProcess(process, 0)); |
| 126 EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); |
| 127 |
| 128 { |
| 129 // Patch TerminateProcess() badly. This turns it into a no-op that returns 0 |
| 130 // without cleaning up arguments from the stack, as a stdcall function is |
| 131 // expected to do. |
| 132 // |
| 133 // This simulates the unexpected cdecl-patched TerminateProcess() as seen at |
| 134 // https://crashpad.chromium.org/bug/179. In reality, this only affects |
| 135 // 32-bit x86, as there’s no calling convention confusion on x86_64. It |
| 136 // doesn’t hurt to run this test in the 64-bit environment, though. |
| 137 const uint8_t patch[] = { |
| 138 #if defined(ARCH_CPU_X86) |
| 139 0x31, 0xc0, // xor eax, eax |
| 140 #elif defined(ARCH_CPU_X86_64) |
| 141 0x48, 0x31, 0xc0, // xor rax, rax |
| 142 #else |
| 143 #error Port |
| 144 #endif |
| 145 0xc3, // ret |
| 146 }; |
| 147 |
| 148 void* target = reinterpret_cast<void*>(TerminateProcess); |
| 149 ScopedExecutablePatch executable_patch(target, patch, arraysize(patch)); |
| 150 |
| 151 // Make sure that SafeTerminateProcess() can be called. Since it’s been |
| 152 // patched with a no-op stub, GetLastError() shouldn’t be modified. |
| 153 SetLastError(ERROR_SUCCESS); |
| 154 EXPECT_FALSE(SafeTerminateProcess(process, 0)); |
| 155 EXPECT_EQ(GetLastError(), ERROR_SUCCESS); |
| 156 } |
| 157 |
| 158 // Now that the real TerminateProcess() has been restored, verify that it |
| 159 // still works properly. |
| 160 SetLastError(ERROR_SUCCESS); |
| 161 EXPECT_FALSE(SafeTerminateProcess(process, 0)); |
| 162 EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); |
| 163 } |
| 164 |
| 165 TEST(SafeTerminateProcess, TerminateChild) { |
| 166 base::FilePath test_executable = TestPaths::Executable(); |
| 167 std::wstring child_executable = |
| 168 test_executable.DirName() |
| 169 .Append(test_executable.BaseName().RemoveFinalExtension().value() + |
| 170 L"_safe_terminate_process_test_child.exe") |
| 171 .value(); |
| 172 |
| 173 ChildLauncher child(child_executable, std::wstring()); |
| 174 ASSERT_NO_FATAL_FAILURE(child.Start()); |
| 175 |
| 176 constexpr DWORD kExitCode = 0x51ee9d1e; // Sort of like “sleep and die.” |
| 177 |
| 178 ASSERT_TRUE(SafeTerminateProcess(child.process_handle(), kExitCode)) |
| 179 << ErrorMessage("TerminateProcess"); |
| 180 EXPECT_EQ(child.WaitForExit(), kExitCode); |
| 181 } |
| 182 |
| 183 } // namespace |
| 184 } // namespace test |
| 185 } // namespace crashpad |
OLD | NEW |