| Index: third_party/crashpad/crashpad/util/win/safe_terminate_process_test.cc
|
| diff --git a/third_party/crashpad/crashpad/util/win/safe_terminate_process_test.cc b/third_party/crashpad/crashpad/util/win/safe_terminate_process_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2fbc8483e9f4757f2f37a5bf7d20fa4d21680d91
|
| --- /dev/null
|
| +++ b/third_party/crashpad/crashpad/util/win/safe_terminate_process_test.cc
|
| @@ -0,0 +1,185 @@
|
| +// Copyright 2017 The Crashpad Authors. All rights reserved.
|
| +//
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +// you may not use this file except in compliance with the License.
|
| +// You may obtain a copy of the License at
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +// distributed under the License is distributed on an "AS IS" BASIS,
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// See the License for the specific language governing permissions and
|
| +// limitations under the License.
|
| +
|
| +#include "util/win/safe_terminate_process.h"
|
| +
|
| +#include <string.h>
|
| +
|
| +#include <string>
|
| +#include <memory>
|
| +
|
| +#include "base/files/file_path.h"
|
| +#include "base/logging.h"
|
| +#include "base/macros.h"
|
| +#include "build/build_config.h"
|
| +#include "gtest/gtest.h"
|
| +#include "test/errors.h"
|
| +#include "test/test_paths.h"
|
| +#include "test/win/child_launcher.h"
|
| +#include "util/win/scoped_handle.h"
|
| +
|
| +namespace crashpad {
|
| +namespace test {
|
| +namespace {
|
| +
|
| +// Patches executable code, saving a copy of the original code so that it can be
|
| +// restored on destruction.
|
| +class ScopedExecutablePatch {
|
| + public:
|
| + ScopedExecutablePatch(void* target, const void* source, size_t size)
|
| + : original_(new uint8_t[size]), target_(target), size_(size) {
|
| + memcpy(original_.get(), target_, size_);
|
| +
|
| + ScopedVirtualProtectRWX protect_rwx(target_, size_);
|
| + memcpy(target_, source, size_);
|
| + }
|
| +
|
| + ~ScopedExecutablePatch() {
|
| + ScopedVirtualProtectRWX protect_rwx(target_, size_);
|
| + memcpy(target_, original_.get(), size_);
|
| + }
|
| +
|
| + private:
|
| + // Sets the protection on (address, size) to PAGE_EXECUTE_READWRITE by calling
|
| + // VirtualProtect(), and restores the original protection on destruction. Note
|
| + // that the region may span multiple pages, but the first page’s original
|
| + // protection will be applied to the entire region on destruction. This
|
| + // shouldn’t be a problem in practice for patching a function for this test’s
|
| + // purposes.
|
| + class ScopedVirtualProtectRWX {
|
| + public:
|
| + // If either the constructor or destructor fails, PCHECK() to terminate
|
| + // immediately, because the process will be in a weird and untrustworthy
|
| + // state, and gtest error handling isn’t worthwhile at that point.
|
| +
|
| + ScopedVirtualProtectRWX(void* address, size_t size)
|
| + : address_(address), size_(size) {
|
| + PCHECK(VirtualProtect(
|
| + address_, size_, PAGE_EXECUTE_READWRITE, &old_protect_))
|
| + << "VirtualProtect";
|
| + }
|
| +
|
| + ~ScopedVirtualProtectRWX() {
|
| + DWORD last_protect_;
|
| + PCHECK(VirtualProtect(address_, size_, old_protect_, &last_protect_))
|
| + << "VirtualProtect";
|
| + }
|
| +
|
| + private:
|
| + void* address_;
|
| + size_t size_;
|
| + DWORD old_protect_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ScopedVirtualProtectRWX);
|
| + };
|
| +
|
| + std::unique_ptr<uint8_t[]> original_;
|
| + void* target_;
|
| + size_t size_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ScopedExecutablePatch);
|
| +};
|
| +
|
| +TEST(SafeTerminateProcess, PatchBadly) {
|
| + // This is a test of SafeTerminateProcess(), but it doesn’t actually terminate
|
| + // anything. Instead, it works with a process handle for the current process
|
| + // that doesn’t have PROCESS_TERMINATE access. The whole point of this test is
|
| + // to patch the real TerminateProcess() badly with a cdecl implementation to
|
| + // ensure that SafeTerminateProcess() can recover from such gross misconduct.
|
| + // The actual termination isn’t relevant to this test.
|
| + //
|
| + // Notably, don’t duplicate the process handle with PROCESS_TERMINATE access
|
| + // or with the DUPLICATE_SAME_ACCESS option. The SafeTerminateProcess() calls
|
| + // that follow operate on a duplicate of the current process’ process handle,
|
| + // and they’re supposed to fail, not terminate this process.
|
| + HANDLE process;
|
| + ASSERT_TRUE(DuplicateHandle(GetCurrentProcess(),
|
| + GetCurrentProcess(),
|
| + GetCurrentProcess(),
|
| + &process,
|
| + PROCESS_QUERY_INFORMATION,
|
| + false,
|
| + 0))
|
| + << ErrorMessage("DuplicateHandle");
|
| + ScopedKernelHANDLE process_owner(process);
|
| +
|
| + // Make sure that TerminateProcess() works as a baseline.
|
| + SetLastError(ERROR_SUCCESS);
|
| + EXPECT_FALSE(TerminateProcess(process, 0));
|
| + EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED);
|
| +
|
| + // Make sure that SafeTerminateProcess() works, calling through to
|
| + // TerminateProcess() properly.
|
| + SetLastError(ERROR_SUCCESS);
|
| + EXPECT_FALSE(SafeTerminateProcess(process, 0));
|
| + EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED);
|
| +
|
| + {
|
| + // Patch TerminateProcess() badly. This turns it into a no-op that returns 0
|
| + // without cleaning up arguments from the stack, as a stdcall function is
|
| + // expected to do.
|
| + //
|
| + // This simulates the unexpected cdecl-patched TerminateProcess() as seen at
|
| + // https://crashpad.chromium.org/bug/179. In reality, this only affects
|
| + // 32-bit x86, as there’s no calling convention confusion on x86_64. It
|
| + // doesn’t hurt to run this test in the 64-bit environment, though.
|
| + const uint8_t patch[] = {
|
| +#if defined(ARCH_CPU_X86)
|
| + 0x31, 0xc0, // xor eax, eax
|
| +#elif defined(ARCH_CPU_X86_64)
|
| + 0x48, 0x31, 0xc0, // xor rax, rax
|
| +#else
|
| +#error Port
|
| +#endif
|
| + 0xc3, // ret
|
| + };
|
| +
|
| + void* target = reinterpret_cast<void*>(TerminateProcess);
|
| + ScopedExecutablePatch executable_patch(target, patch, arraysize(patch));
|
| +
|
| + // Make sure that SafeTerminateProcess() can be called. Since it’s been
|
| + // patched with a no-op stub, GetLastError() shouldn’t be modified.
|
| + SetLastError(ERROR_SUCCESS);
|
| + EXPECT_FALSE(SafeTerminateProcess(process, 0));
|
| + EXPECT_EQ(GetLastError(), ERROR_SUCCESS);
|
| + }
|
| +
|
| + // Now that the real TerminateProcess() has been restored, verify that it
|
| + // still works properly.
|
| + SetLastError(ERROR_SUCCESS);
|
| + EXPECT_FALSE(SafeTerminateProcess(process, 0));
|
| + EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED);
|
| +}
|
| +
|
| +TEST(SafeTerminateProcess, TerminateChild) {
|
| + base::FilePath test_executable = TestPaths::Executable();
|
| + std::wstring child_executable =
|
| + test_executable.DirName()
|
| + .Append(test_executable.BaseName().RemoveFinalExtension().value() +
|
| + L"_safe_terminate_process_test_child.exe")
|
| + .value();
|
| +
|
| + ChildLauncher child(child_executable, std::wstring());
|
| + ASSERT_NO_FATAL_FAILURE(child.Start());
|
| +
|
| + constexpr DWORD kExitCode = 0x51ee9d1e; // Sort of like “sleep and die.”
|
| +
|
| + ASSERT_TRUE(SafeTerminateProcess(child.process_handle(), kExitCode))
|
| + << ErrorMessage("TerminateProcess");
|
| + EXPECT_EQ(child.WaitForExit(), kExitCode);
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace test
|
| +} // namespace crashpad
|
|
|