// 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 #include #include #include #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(MEM_COMMIT)); EXPECT_EQ(mi.Protect, static_cast(PAGE_EXECUTE_READ)); EXPECT_EQ(mi.Type, static_cast(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 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(GetModuleHandle(nullptr))); EXPECT_EQ(modules[1].dll_base, reinterpret_cast(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(_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 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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap(CheckedRange(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> result = GetReadableRangesOfMemoryMap( CheckedRange(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(reserve_region); void* readable1 = VirtualAlloc(reinterpret_cast(reserved_as_int + kBlockSize), kBlockSize, MEM_COMMIT, PAGE_READWRITE); ASSERT_TRUE(readable1); void* readable2 = VirtualAlloc(reinterpret_cast(reserved_as_int + (kBlockSize * 3)), kBlockSize * 2, MEM_COMMIT, PAGE_READWRITE); ASSERT_TRUE(readable2); void* no_access = VirtualAlloc(reinterpret_cast(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(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. std::unique_ptr into(new unsigned char[kBlockSize * 6]); SIZE_T bytes_read; EXPECT_TRUE(ReadProcessMemory( current_process, readable1, into.get(), kBlockSize, &bytes_read)); EXPECT_EQ(bytes_read, kBlockSize); EXPECT_TRUE(ReadProcessMemory( current_process, readable2, into.get(), kBlockSize * 2, &bytes_read)); EXPECT_EQ(bytes_read, kBlockSize * 2); EXPECT_FALSE(ReadProcessMemory( current_process, no_access, into.get(), kBlockSize, &bytes_read)); EXPECT_FALSE(ReadProcessMemory( current_process, reserve_region, into.get(), kBlockSize, &bytes_read)); EXPECT_FALSE(ReadProcessMemory(current_process, reserve_region, into.get(), kBlockSize * 6, &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(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(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(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(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) { constexpr size_t kAllocationSize = 12345; std::unique_ptr safe_memory(new char[kAllocationSize]); ProcessInfo info; info.Initialize(GetCurrentProcess()); EXPECT_TRUE( info.LoggingRangeIsFullyReadable(CheckedRange( FromPointerCast(safe_memory.get()), kAllocationSize))); EXPECT_FALSE(info.LoggingRangeIsFullyReadable( CheckedRange(0, 1024))); } } // namespace } // namespace test } // namespace crashpad