blob: 13a0a95dadd38b9b762b42aaaa8293ce087f13a2 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <elf-search.h>
#include <elf.h>
#include <lib/fdio/spawn.h>
#include <lib/fit/defer.h>
#include <lib/zx/exception.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <unistd.h>
#include <zircon/syscalls/exception.h>
#include <zxtest/zxtest.h>
#include "zircon/system/ulib/c/scudo/gwp_asan_info.h"
namespace {
constexpr const char* kHelperPath = "/pkg/bin/gwp-asan-test-use-after-free";
TEST(GwpAsanTest, HandleGwpAsanException) {
if constexpr (!HAS_GWP_ASAN) {
return;
}
// Create a job and attach an exception channel.
zx::job test_job;
ASSERT_OK(zx::job::create(*zx::job::default_job(), 0, &test_job));
auto auto_call_kill_job = fit::defer([&test_job]() { test_job.kill(); });
zx::channel exception_channel;
ASSERT_OK(test_job.create_exception_channel(0, &exception_channel));
// Spawn the helper process.
const char* argv[] = {kHelperPath, nullptr};
const char* envp[] = {
"SCUDO_OPTIONS="
"GWP_ASAN_Enabled=true:GWP_ASAN_SampleRate=1:GWP_ASAN_MaxSimultaneousAllocations=512",
nullptr};
zx::process test_process;
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
ASSERT_OK(fdio_spawn_etc(test_job.get(), FDIO_SPAWN_CLONE_ALL, kHelperPath, argv, envp, 0,
nullptr, test_process.reset_and_get_address(), err_msg),
"%s", err_msg);
// Wait for the helper to crash or the process to terminate.
zx_wait_item_t wait_items[] = {
{.handle = exception_channel.get(), .waitfor = ZX_CHANNEL_READABLE, .pending = 0},
{.handle = test_process.get(), .waitfor = ZX_PROCESS_TERMINATED, .pending = 0},
};
ASSERT_OK(zx_object_wait_many(wait_items, 2, ZX_TIME_INFINITE));
// The helper should crash and the exception channel should signal.
ASSERT_TRUE(wait_items[0].pending & ZX_CHANNEL_READABLE);
ASSERT_FALSE(wait_items[1].pending & ZX_PROCESS_TERMINATED);
zx_exception_info_t info;
zx::exception exception;
ASSERT_OK(exception_channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1,
nullptr, nullptr));
ASSERT_EQ(ZX_EXCP_FATAL_PAGE_FAULT, info.type);
// The address of __libc_gwp_asan_info.
uint64_t libc_gwp_asan_info_addr = 0;
// Find the GWP-ASan note.
elf_search::ForEachModule(test_process, [&](const elf_search::ModuleInfo& info) {
if (info.name != "libc.so") {
return;
}
for (const auto& phdr : info.phdrs) {
if (phdr.p_type != PT_NOTE) {
continue;
}
// Read the whole segment.
std::vector<std::byte> notes(phdr.p_memsz);
size_t actual;
ASSERT_OK(
test_process.read_memory(info.vaddr + phdr.p_vaddr, notes.data(), notes.size(), &actual));
ASSERT_EQ(notes.size(), actual);
uint64_t p = 0;
while (p + sizeof(Elf64_Nhdr) <= notes.size()) {
Elf64_Nhdr& nhdr = reinterpret_cast<Elf64_Nhdr&>(notes[p]);
p += sizeof(Elf64_Nhdr);
p += (nhdr.n_namesz + 3) & ~3;
if (nhdr.n_type == GWP_ASAN_NOTE_TYPE) {
ASSERT_EQ(sizeof(uint64_t), nhdr.n_descsz);
ASSERT_TRUE(p + nhdr.n_descsz <= notes.size());
libc_gwp_asan_info_addr =
reinterpret_cast<uint64_t&>(notes[p]) + p + info.vaddr + phdr.p_vaddr;
return;
}
p += (nhdr.n_descsz + 3) & ~3;
}
}
});
// Read the __libc_gwp_asan_info.
ASSERT_NE(0, libc_gwp_asan_info_addr);
gwp_asan::LibcGwpAsanInfo libc_gwp_asan_info;
size_t actual;
ASSERT_OK(test_process.read_memory(libc_gwp_asan_info_addr, &libc_gwp_asan_info,
sizeof(libc_gwp_asan_info), &actual));
ASSERT_EQ(actual, sizeof(libc_gwp_asan_info));
// Read the allocator state and the allocator metadata.
gwp_asan::AllocatorState state;
ASSERT_OK(test_process.read_memory(reinterpret_cast<uintptr_t>(libc_gwp_asan_info.state), &state,
sizeof(state), &actual));
ASSERT_EQ(actual, sizeof(state));
std::vector<gwp_asan::AllocationMetadata> metadata_list(state.MaxSimultaneousAllocations);
ASSERT_OK(test_process.read_memory(
reinterpret_cast<uintptr_t>(libc_gwp_asan_info.metadata), metadata_list.data(),
sizeof(gwp_asan::AllocationMetadata) * metadata_list.size(), &actual));
ASSERT_EQ(actual, sizeof(gwp_asan::AllocationMetadata) * metadata_list.size());
// Magic and version should match.
ASSERT_EQ(0, memcmp(gwp_asan::AllocatorVersionMagic::kAllocatorVersionMagic,
state.VersionMagic.Magic, 4));
ASSERT_EQ(gwp_asan::AllocatorVersionMagic::kAllocatorVersion, state.VersionMagic.Version);
// Since it's a use-after-free, ErrorPtr must be provided for the correct detection.
ASSERT_FALSE(__gwp_asan_error_is_mine(&state, 0));
// And it's not an internal error.
ASSERT_EQ(0, __gwp_asan_get_internal_crash_address(&state,
state.internallyDetectedErrorFaultAddress()));
// Read the faulting address.
zx::thread thread;
ASSERT_OK(exception.get_thread(&thread));
zx_exception_report_t exception_report;
ASSERT_OK(thread.get_info(ZX_INFO_THREAD_EXCEPTION_REPORT, &exception_report,
sizeof(exception_report), nullptr, nullptr));
#if defined(__x86_64__)
uint64_t faulting_addr = exception_report.context.arch.u.x86_64.cr2;
#elif defined(__aarch64__)
uint64_t faulting_addr = exception_report.context.arch.u.arm_64.far;
#elif defined(__riscv)
uint64_t faulting_addr = exception_report.context.arch.u.riscv_64.tval;
#else
#error "what machine?"
#endif
// Now we should be able to obtain the full report of the crash.
ASSERT_TRUE(__gwp_asan_error_is_mine(&state, faulting_addr));
ASSERT_EQ(gwp_asan::Error::USE_AFTER_FREE,
__gwp_asan_diagnose_error(&state, metadata_list.data(), faulting_addr));
const gwp_asan::AllocationMetadata* metadata =
__gwp_asan_get_metadata(&state, metadata_list.data(), faulting_addr);
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(__gwp_asan_is_deallocated(metadata));
// Allocation and free backtraces should contain at least 3 frames.
uintptr_t backtrace[16];
ASSERT_GE(__gwp_asan_get_allocation_trace(metadata, backtrace, 16), 3);
ASSERT_GE(__gwp_asan_get_deallocation_trace(metadata, backtrace, 16), 3);
}
} // namespace