chromium/third_party/crashpad/crashpad/util/win/safe_terminate_process_test.cc

// Copyright 2017 The Crashpad Authors
//
// 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 <iterator>
#include <memory>
#include <string>

#include "base/check.h"
#include "base/files/file_path.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(const ScopedExecutablePatch&) = delete;
  ScopedExecutablePatch& operator=(const ScopedExecutablePatch&) = delete;

  ~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 Google Test 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(const ScopedVirtualProtectRWX&) = delete;
    ScopedVirtualProtectRWX& operator=(const ScopedVirtualProtectRWX&) = delete;

    ~ScopedVirtualProtectRWX() {
      DWORD last_protect_;
      PCHECK(VirtualProtect(address_, size_, old_protect_, &last_protect_))
          << "VirtualProtect";
    }

   private:
    void* address_;
    size_t size_;
    DWORD old_protect_;
  };

  std::unique_ptr<uint8_t[]> original_;
  void* target_;
  size_t size_;
};

// SafeTerminateProcess is calling convention specific only for x86.
#if defined(ARCH_CPU_X86_FAMILY)
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(), static_cast<DWORD>(ERROR_ACCESS_DENIED));

  // Make sure that SafeTerminateProcess() works, calling through to
  // TerminateProcess() properly.
  SetLastError(ERROR_SUCCESS);
  EXPECT_FALSE(SafeTerminateProcess(process, 0));
  EXPECT_EQ(GetLastError(), static_cast<DWORD>(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.
    static constexpr 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, std::size(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(), static_cast<DWORD>(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(), static_cast<DWORD>(ERROR_ACCESS_DENIED));
}
#endif  // ARCH_CPU_X86_FAMILY

TEST(SafeTerminateProcess, TerminateChild) {
  base::FilePath child_executable =
      TestPaths::BuildArtifact(L"util",
                               L"safe_terminate_process_test_child",
                               TestPaths::FileType::kExecutable);
  ChildLauncher child(child_executable, L"");
  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