// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/memory/partition_allocator/inspect_utils.h"
#include <libproc.h>
#include <mach/mach_traps.h>
#include <mach/mach_vm.h>
#include <sys/mman.h>
#include "base/check_op.h"
#include "base/debug/proc_maps_linux.h"
#include "base/logging.h"
#include "base/memory/page_size.h"
#include "partition_alloc/thread_cache.h"
namespace partition_alloc::tools {
RemoteProcessMemoryReader::RemoteProcessMemoryReader(pid_t pid) : pid_(pid) {
kern_return_t ret = task_for_pid(mach_task_self(), pid_, &task_);
is_valid_ = ret == KERN_SUCCESS;
}
bool RemoteProcessMemoryReader::ReadMemory(uintptr_t remote_address,
size_t size,
char* buffer) {
mach_vm_size_t read_bytes = size;
kern_return_t ret = mach_vm_read_overwrite(
task_, remote_address, size, reinterpret_cast<mach_vm_address_t>(buffer),
&read_bytes);
if (ret != KERN_SUCCESS) {
// Try to read page by page.
//
// It seems that mach_vm_read() doesn't work when the target mapping is not
// readable. Since superpages always have at least a couple of guard pages,
// we need to read page by page.
size_t page_count = size / base::GetPageSize();
CHECK_EQ(0u, size % base::GetPageSize());
size_t read_pages = 0;
size_t page_size = base::GetPageSize();
for (size_t i = 0; i < page_count; i++) {
size_t offset = i * page_size;
auto target_address =
reinterpret_cast<mach_vm_address_t>(buffer + offset);
auto source_address = remote_address + offset;
ret = mach_vm_read_overwrite(task_, source_address, page_size,
target_address, &read_bytes);
if (ret == KERN_SUCCESS)
read_pages++;
}
LOG(WARNING) << "Couldn't read all pages. Page count = " << page_count
<< " Read count = " << read_pages;
return read_pages != 0;
}
return ret == KERN_SUCCESS;
}
base::ScopedFD OpenPagemap(pid_t pid) {
// Not supported.
return base::ScopedFD(-1);
}
uintptr_t IndexThreadCacheNeedleArray(RemoteProcessMemoryReader& reader,
size_t index) {
task_t task;
kern_return_t ret = task_for_pid(mach_task_self(), reader.pid(), &task);
CHECK_EQ(ret, KERN_SUCCESS)
<< "Is the binary signed? codesign --force --deep -s - "
<< "out/Default/pa_tcache_inspect to sign it";
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
while (true) {
address += size;
vm_region_extended_info_data_t info;
mach_port_t object_name;
mach_msg_type_number_t count;
count = VM_REGION_EXTENDED_INFO_COUNT;
ret = mach_vm_region(task, &address, &size, VM_REGION_EXTENDED_INFO,
reinterpret_cast<vm_region_info_t>(&info), &count,
&object_name);
if (ret != KERN_SUCCESS) {
LOG(ERROR) << "Cannot read region";
return 0;
}
// The needle is in the .data region, which is mapped Copy On Write from the
// binary, and is Readable and Writable.
if ((info.protection != (VM_PROT_READ | VM_PROT_WRITE)) ||
(info.share_mode != SM_COW))
continue;
char buf[PATH_MAX];
int len = proc_regionfilename(reader.pid(), address, buf, sizeof(buf));
buf[len] = '\0';
// Should be in the framework, not the launcher binary.
if (len == 0 || !strstr(buf, "Chromium Framework"))
continue;
// We have a candidate, let's look into it.
LOG(INFO) << "Found a candidate region between " << std::hex << address
<< " and " << address + size << std::dec << " (size = " << size
<< ") path = " << buf;
// Scan the region, looking for the needles.
uintptr_t needle_array_candidate[kThreadCacheNeedleArraySize];
for (uintptr_t addr = address;
addr < address + size - sizeof(needle_array_candidate);
addr += sizeof(uintptr_t)) {
bool ok = reader.ReadMemory(
reinterpret_cast<unsigned long>(addr), sizeof(needle_array_candidate),
reinterpret_cast<char*>(needle_array_candidate));
if (!ok) {
LOG(WARNING) << "Failed to read";
continue;
}
if (needle_array_candidate[0] == kNeedle1 &&
needle_array_candidate[kThreadCacheNeedleArraySize - 1] == kNeedle2) {
LOG(INFO) << "Got it! Address = 0x" << std::hex
<< needle_array_candidate[index];
return needle_array_candidate[index];
}
}
}
}
} // namespace partition_alloc::tools