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

// Copyright 2015 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/process_info.h"

#include <dbghelp.h>
#include <intrin.h>
#include <wchar.h>

#include <memory>

#include "base/containers/heap_array.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/scoped_temp_dir.h"
#include "test/test_paths.h"
#include "test/win/child_launcher.h"
#include "util/file/file_io.h"
#include "util/misc/from_pointer_cast.h"
#include "util/misc/random_string.h"
#include "util/misc/uuid.h"
#include "util/win/command_line.h"
#include "util/win/get_function.h"
#include "util/win/handle.h"
#include "util/win/scoped_handle.h"
#include "util/win/scoped_registry_key.h"

namespace crashpad {
namespace test {
namespace {

constexpr wchar_t kNtdllName[] = L"\\ntdll.dll";

#if !defined(ARCH_CPU_64_BITS)
bool IsProcessWow64(HANDLE process_handle) {
  static const auto is_wow64_process =
      GET_FUNCTION(L"kernel32.dll", ::IsWow64Process);
  if (!is_wow64_process)
    return false;
  BOOL is_wow64;
  if (!is_wow64_process(process_handle, &is_wow64)) {
    PLOG(ERROR) << "IsWow64Process";
    return false;
  }
  return !!is_wow64;
}
#endif

void VerifyAddressInInCodePage(const ProcessInfo& process_info,
                               WinVMAddress code_address) {
  // Make sure the child code address is an code page address with the right
  // information.
  const ProcessInfo::MemoryBasicInformation64Vector& memory_info =
      process_info.MemoryInfo();
  bool found_region = false;
  for (const auto& mi : memory_info) {
    if (mi.BaseAddress <= code_address &&
        mi.BaseAddress + mi.RegionSize > code_address) {
      EXPECT_EQ(mi.State, static_cast<DWORD>(MEM_COMMIT));
      EXPECT_EQ(mi.Protect, static_cast<DWORD>(PAGE_EXECUTE_READ));
      EXPECT_EQ(mi.Type, static_cast<DWORD>(MEM_IMAGE));
      EXPECT_FALSE(found_region);
      found_region = true;
    }
  }
  EXPECT_TRUE(found_region);
}

TEST(ProcessInfo, Self) {
  ProcessInfo process_info;
  ASSERT_TRUE(process_info.Initialize(GetCurrentProcess()));
  EXPECT_EQ(process_info.ProcessID(), GetCurrentProcessId());
  EXPECT_GT(process_info.ParentProcessID(), 0u);

#if defined(ARCH_CPU_64_BITS)
  EXPECT_TRUE(process_info.Is64Bit());
  EXPECT_FALSE(process_info.IsWow64());
#else
  EXPECT_FALSE(process_info.Is64Bit());
  if (IsProcessWow64(GetCurrentProcess()))
    EXPECT_TRUE(process_info.IsWow64());
  else
    EXPECT_FALSE(process_info.IsWow64());
#endif

  std::wstring command_line;
  EXPECT_TRUE(process_info.CommandLine(&command_line));
  EXPECT_EQ(command_line, std::wstring(GetCommandLine()));

  std::vector<ProcessInfo::Module> modules;
  EXPECT_TRUE(process_info.Modules(&modules));
  ASSERT_GE(modules.size(), 2u);
  std::wstring self_name =
      std::wstring(1, '\\') +
      TestPaths::ExpectedExecutableBasename(L"crashpad_util_test").value();
  ASSERT_GE(modules[0].name.size(), self_name.size());
  EXPECT_EQ(modules[0].name.substr(modules[0].name.size() - self_name.size()),
            self_name);
  ASSERT_GE(modules[1].name.size(), wcslen(kNtdllName));
  EXPECT_EQ(modules[1].name.substr(modules[1].name.size() - wcslen(kNtdllName)),
            kNtdllName);

  EXPECT_EQ(modules[0].dll_base,
            reinterpret_cast<uintptr_t>(GetModuleHandle(nullptr)));
  EXPECT_EQ(modules[1].dll_base,
            reinterpret_cast<uintptr_t>(GetModuleHandle(L"ntdll.dll")));

  EXPECT_GT(modules[0].size, 0u);
  EXPECT_GT(modules[1].size, 0u);

  EXPECT_EQ(modules[0].timestamp,
            GetTimestampForLoadedLibrary(GetModuleHandle(nullptr)));
  // System modules are forced to particular stamps and the file header values
  // don't match the on-disk times. Just make sure we got some data here.
  EXPECT_GT(modules[1].timestamp, 0);

  // Find something we know is a code address and confirm expected memory
  // information settings.
  VerifyAddressInInCodePage(process_info,
                            FromPointerCast<WinVMAddress>(_ReturnAddress()));
}

void TestOtherProcess(TestPaths::Architecture architecture) {
  ProcessInfo process_info;

  UUID done_uuid;
  done_uuid.InitializeWithNew();

  ScopedKernelHANDLE done(
      CreateEvent(nullptr, true, false, done_uuid.ToWString().c_str()));
  ASSERT_TRUE(done.get()) << ErrorMessage("CreateEvent");

  base::FilePath child_test_executable =
      TestPaths::BuildArtifact(L"util",
                               L"process_info_test_child",
                               TestPaths::FileType::kExecutable,
                               architecture);
  std::wstring args;
  AppendCommandLineArgument(done_uuid.ToWString(), &args);

  ChildLauncher child(child_test_executable, args);
  ASSERT_NO_FATAL_FAILURE(child.Start());

  // The child sends us a code address we can look up in the memory map.
  WinVMAddress code_address;
  CheckedReadFileExactly(
      child.stdout_read_handle(), &code_address, sizeof(code_address));

  ASSERT_TRUE(process_info.Initialize(child.process_handle()));

  // Tell the test it's OK to shut down now that we've read our data.
  EXPECT_TRUE(SetEvent(done.get())) << ErrorMessage("SetEvent");

  EXPECT_EQ(child.WaitForExit(), 0u);

  std::vector<ProcessInfo::Module> modules;
  EXPECT_TRUE(process_info.Modules(&modules));
  ASSERT_GE(modules.size(), 3u);
  std::wstring child_name = L"\\crashpad_util_test_process_info_test_child.exe";
  ASSERT_GE(modules[0].name.size(), child_name.size());
  EXPECT_EQ(modules[0].name.substr(modules[0].name.size() - child_name.size()),
            child_name);
  ASSERT_GE(modules[1].name.size(), wcslen(kNtdllName));
  EXPECT_EQ(modules[1].name.substr(modules[1].name.size() - wcslen(kNtdllName)),
            kNtdllName);
  // lz32.dll is an uncommonly-used-but-always-available module that the test
  // binary manually loads.
  static constexpr wchar_t kLz32dllName[] = L"\\lz32.dll";
  auto& lz32 = modules[modules.size() - 2];
  ASSERT_GE(lz32.name.size(), wcslen(kLz32dllName));
  EXPECT_EQ(lz32.name.substr(lz32.name.size() - wcslen(kLz32dllName)),
            kLz32dllName);

  // Note that the test code corrupts the PEB MemoryOrder list, whereas
  // ProcessInfo::Modules() retrieves the module names via the PEB LoadOrder
  // list. These are expected to point to the same strings, but theoretically
  // could be separate.
  auto& corrupted = modules.back();
  EXPECT_EQ(corrupted.name, L"???");

  VerifyAddressInInCodePage(process_info, code_address);
}

TEST(ProcessInfo, OtherProcess) {
  TestOtherProcess(TestPaths::Architecture::kDefault);
}

#if defined(ARCH_CPU_64_BITS)
TEST(ProcessInfo, OtherProcessWOW64) {
  if (!TestPaths::Has32BitBuildArtifacts()) {
    GTEST_SKIP();
  }

  TestOtherProcess(TestPaths::Architecture::k32Bit);
}
#endif  // ARCH_CPU_64_BITS

TEST(ProcessInfo, AccessibleRangesNone) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_FREE;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(2, 4),
                                   memory_info);

  EXPECT_TRUE(result.empty());
}

TEST(ProcessInfo, AccessibleRangesOneInside) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(2, 4),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 2u);
  EXPECT_EQ(result[0].size(), 4u);
}

TEST(ProcessInfo, AccessibleRangesOneTruncatedSize) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 10;
  mbi.RegionSize = 20;
  mbi.State = MEM_FREE;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 5u);
  EXPECT_EQ(result[0].size(), 5u);
}

TEST(ProcessInfo, AccessibleRangesOneMovedStart) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_FREE;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 10;
  mbi.RegionSize = 20;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 10u);
  EXPECT_EQ(result[0].size(), 5u);
}

TEST(ProcessInfo, ReserveIsInaccessible) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_RESERVE;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 10;
  mbi.RegionSize = 20;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 10u);
  EXPECT_EQ(result[0].size(), 5u);
}

TEST(ProcessInfo, PageGuardIsInaccessible) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_COMMIT;
  mbi.Protect = PAGE_GUARD;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 10;
  mbi.RegionSize = 20;
  mbi.State = MEM_COMMIT;
  mbi.Protect = 0;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 10u);
  EXPECT_EQ(result[0].size(), 5u);
}

TEST(ProcessInfo, PageNoAccessIsInaccessible) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_COMMIT;
  mbi.Protect = PAGE_NOACCESS;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 10;
  mbi.RegionSize = 20;
  mbi.State = MEM_COMMIT;
  mbi.Protect = 0;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 10u);
  EXPECT_EQ(result[0].size(), 5u);
}

TEST(ProcessInfo, AccessibleRangesCoalesced) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_FREE;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 10;
  mbi.RegionSize = 2;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 12;
  mbi.RegionSize = 5;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(11, 4),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 11u);
  EXPECT_EQ(result[0].size(), 4u);
}

TEST(ProcessInfo, AccessibleRangesMiddleUnavailable) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 0;
  mbi.RegionSize = 10;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 10;
  mbi.RegionSize = 5;
  mbi.State = MEM_FREE;
  memory_info.push_back(mbi);

  mbi.BaseAddress = 15;
  mbi.RegionSize = 100;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 45),
                                   memory_info);

  ASSERT_EQ(result.size(), 2u);
  EXPECT_EQ(result[0].base(), 5u);
  EXPECT_EQ(result[0].size(), 5u);
  EXPECT_EQ(result[1].base(), 15u);
  EXPECT_EQ(result[1].size(), 35u);
}

TEST(ProcessInfo, RequestedBeforeMap) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 10;
  mbi.RegionSize = 10;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10),
                                   memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 10u);
  EXPECT_EQ(result[0].size(), 5u);
}

TEST(ProcessInfo, RequestedAfterMap) {
  ProcessInfo::MemoryBasicInformation64Vector memory_info;
  MEMORY_BASIC_INFORMATION64 mbi = {0};

  mbi.BaseAddress = 10;
  mbi.RegionSize = 10;
  mbi.State = MEM_COMMIT;
  memory_info.push_back(mbi);

  std::vector<CheckedRange<WinVMAddress, WinVMSize>> result =
      GetReadableRangesOfMemoryMap(
          CheckedRange<WinVMAddress, WinVMSize>(15, 100), memory_info);

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0].base(), 15u);
  EXPECT_EQ(result[0].size(), 5u);
}

TEST(ProcessInfo, ReadableRanges) {
  SYSTEM_INFO system_info;
  GetSystemInfo(&system_info);

  const size_t kBlockSize = system_info.dwPageSize;

  // Allocate 6 pages, and then commit the second, fourth, and fifth, and mark
  // two as committed, but PAGE_NOACCESS, so we have a setup like this:
  // 0       1       2       3       4       5
  // +-----------------------------------------------+
  // | ????? |       | xxxxx |       |       | ????? |
  // +-----------------------------------------------+
  void* reserve_region =
      VirtualAlloc(nullptr, kBlockSize * 6, MEM_RESERVE, PAGE_READWRITE);
  ASSERT_TRUE(reserve_region);
  uintptr_t reserved_as_int = reinterpret_cast<uintptr_t>(reserve_region);
  void* readable1 =
      VirtualAlloc(reinterpret_cast<void*>(reserved_as_int + kBlockSize),
                   kBlockSize,
                   MEM_COMMIT,
                   PAGE_READWRITE);
  ASSERT_TRUE(readable1);
  void* readable2 =
      VirtualAlloc(reinterpret_cast<void*>(reserved_as_int + (kBlockSize * 3)),
                   kBlockSize * 2,
                   MEM_COMMIT,
                   PAGE_READWRITE);
  ASSERT_TRUE(readable2);

  void* no_access =
      VirtualAlloc(reinterpret_cast<void*>(reserved_as_int + (kBlockSize * 2)),
                   kBlockSize,
                   MEM_COMMIT,
                   PAGE_NOACCESS);
  ASSERT_TRUE(no_access);

  HANDLE current_process = GetCurrentProcess();
  ProcessInfo info;
  info.Initialize(current_process);
  auto ranges = info.GetReadableRanges(
      CheckedRange<WinVMAddress, WinVMSize>(reserved_as_int, kBlockSize * 6));

  ASSERT_EQ(ranges.size(), 2u);
  EXPECT_EQ(ranges[0].base(), reserved_as_int + kBlockSize);
  EXPECT_EQ(ranges[0].size(), kBlockSize);
  EXPECT_EQ(ranges[1].base(), reserved_as_int + (kBlockSize * 3));
  EXPECT_EQ(ranges[1].size(), kBlockSize * 2);

  // Also make sure what we think we can read corresponds with what we can
  // actually read.
  auto into = base::HeapArray<unsigned char>::Uninit(kBlockSize * 6);
  SIZE_T bytes_read;

  EXPECT_TRUE(ReadProcessMemory(
      current_process, readable1, into.data(), kBlockSize, &bytes_read));
  EXPECT_EQ(bytes_read, kBlockSize);

  EXPECT_TRUE(ReadProcessMemory(
      current_process, readable2, into.data(), kBlockSize * 2, &bytes_read));
  EXPECT_EQ(bytes_read, kBlockSize * 2);

  EXPECT_FALSE(ReadProcessMemory(
      current_process, no_access, into.data(), kBlockSize, &bytes_read));
  EXPECT_FALSE(ReadProcessMemory(
      current_process, reserve_region, into.data(), kBlockSize, &bytes_read));
  EXPECT_FALSE(ReadProcessMemory(
      current_process, reserve_region, into.data(), into.size(), &bytes_read));
}

TEST(ProcessInfo, Handles) {
  ScopedTempDir temp_dir;

  ScopedFileHandle file(LoggingOpenFileForWrite(
      temp_dir.path().Append(FILE_PATH_LITERAL("test_file")),
      FileWriteMode::kTruncateOrCreate,
      FilePermissions::kWorldReadable));
  ASSERT_TRUE(file.is_valid());

  SECURITY_ATTRIBUTES security_attributes = {0};
  security_attributes.nLength = sizeof(security_attributes);
  security_attributes.bInheritHandle = true;
  ScopedFileHandle inherited_file(CreateFile(
      temp_dir.path().Append(FILE_PATH_LITERAL("inheritable")).value().c_str(),
      GENERIC_WRITE,
      0,
      &security_attributes,
      CREATE_NEW,
      FILE_ATTRIBUTE_NORMAL,
      nullptr));
  ASSERT_TRUE(inherited_file.is_valid());

  HKEY key;
  ASSERT_EQ(RegOpenKeyEx(
                HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft", 0, KEY_READ, &key),
            ERROR_SUCCESS);
  ScopedRegistryKey scoped_key(key);
  ASSERT_TRUE(scoped_key.is_valid());

  std::wstring mapping_name =
      base::UTF8ToWide(base::StringPrintf("Local\\test_mapping_%lu_%s",
                                          GetCurrentProcessId(),
                                          RandomString().c_str()));
  ScopedKernelHANDLE mapping(CreateFileMapping(INVALID_HANDLE_VALUE,
                                               nullptr,
                                               PAGE_READWRITE,
                                               0,
                                               1024,
                                               mapping_name.c_str()));
  ASSERT_TRUE(mapping.is_valid()) << ErrorMessage("CreateFileMapping");

  ProcessInfo info;
  info.Initialize(GetCurrentProcess());
  bool found_file_handle = false;
  bool found_inherited_file_handle = false;
  bool found_key_handle = false;
  bool found_mapping_handle = false;
  for (auto handle : info.Handles()) {
    if (handle.handle == HandleToInt(file.get())) {
      EXPECT_FALSE(found_file_handle);
      found_file_handle = true;
      EXPECT_EQ(handle.type_name, L"File");
      EXPECT_EQ(handle.handle_count, 1u);
      EXPECT_NE(handle.pointer_count, 0u);
      EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL,
                static_cast<uint32_t>(STANDARD_RIGHTS_READ |
                                      STANDARD_RIGHTS_WRITE | SYNCHRONIZE));
      EXPECT_EQ(handle.attributes, 0u);
    }
    if (handle.handle == HandleToInt(inherited_file.get())) {
      EXPECT_FALSE(found_inherited_file_handle);
      found_inherited_file_handle = true;
      EXPECT_EQ(handle.type_name, L"File");
      EXPECT_EQ(handle.handle_count, 1u);
      EXPECT_NE(handle.pointer_count, 0u);
      EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL,
                static_cast<uint32_t>(STANDARD_RIGHTS_READ |
                                      STANDARD_RIGHTS_WRITE | SYNCHRONIZE));

      // OBJ_INHERIT from ntdef.h, but including that conflicts with other
      // headers.
      constexpr uint32_t kObjInherit = 0x2;
      EXPECT_EQ(handle.attributes, kObjInherit);
    }
    if (handle.handle == HandleToInt(scoped_key.get())) {
      EXPECT_FALSE(found_key_handle);
      found_key_handle = true;
      EXPECT_EQ(handle.type_name, L"Key");
      EXPECT_EQ(handle.handle_count, 1u);
      EXPECT_NE(handle.pointer_count, 0u);
      EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL,
                static_cast<uint32_t>(STANDARD_RIGHTS_READ));
      EXPECT_EQ(handle.attributes, 0u);
    }
    if (handle.handle == HandleToInt(mapping.get())) {
      EXPECT_FALSE(found_mapping_handle);
      found_mapping_handle = true;
      EXPECT_EQ(handle.type_name, L"Section");
      EXPECT_EQ(handle.handle_count, 1u);
      EXPECT_NE(handle.pointer_count, 0u);
      EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL,
                static_cast<uint32_t>(DELETE | READ_CONTROL | WRITE_DAC |
                                      WRITE_OWNER | STANDARD_RIGHTS_READ |
                                      STANDARD_RIGHTS_WRITE));
      EXPECT_EQ(handle.attributes, 0u);
    }
  }
  EXPECT_TRUE(found_file_handle);
  EXPECT_TRUE(found_inherited_file_handle);
  EXPECT_TRUE(found_key_handle);
  EXPECT_TRUE(found_mapping_handle);
}

TEST(ProcessInfo, OutOfRangeCheck) {
  auto safe_memory = base::HeapArray<char>::Uninit(12345);

  ProcessInfo info;
  info.Initialize(GetCurrentProcess());

  EXPECT_TRUE(
      info.LoggingRangeIsFullyReadable(CheckedRange<WinVMAddress, WinVMSize>(
          FromPointerCast<WinVMAddress>(safe_memory.data()),
          safe_memory.size())));
  EXPECT_FALSE(info.LoggingRangeIsFullyReadable(
      CheckedRange<WinVMAddress, WinVMSize>(0, 1024)));
}

}  // namespace
}  // namespace test
}  // namespace crashpad