blob: 8cdbef46f998eaae5005a94a91999fa554eb45ac [file] [log] [blame]
// Copyright 2022 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 <lib/arch/arm64/system.h>
#include <lib/elf-psabi/sp.h>
#include <lib/fit/defer.h>
#include <lib/zircon-internal/default_stack_size.h>
#include <lib/zx/exception.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <zircon/features.h>
#include <zircon/hw/debug/arm64.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <test-utils/test-utils.h>
#include <zxtest/zxtest.h>
namespace {
#if defined(__aarch64__)
constexpr size_t kTagShift = 56;
constexpr uint8_t kTestTag = 0xAB;
constexpr uint64_t AddTag(uintptr_t ptr, uint8_t tag) {
constexpr uint64_t kTagMask = UINT64_C(0xff) << kTagShift;
ZX_ASSERT((kTagMask & ptr) == 0 && "Expected an untagged pointer.");
return (static_cast<uint64_t>(tag) << kTagShift) | static_cast<uint64_t>(ptr);
}
template <typename T>
T* AddTag(T* ptr, uint8_t tag) {
return reinterpret_cast<T*>(AddTag(reinterpret_cast<uintptr_t>(ptr), tag));
}
// Disable sanitizers for this because any sanitizer that involves doing a
// right shift to get a shadow memory location could cause a tag to leak into
// bit 55, leading to an incorrect shadow being referenced. This will affect
// ASan and eventually HWASan.
#ifdef __clang__
[[clang::no_sanitize("all")]]
#endif
void DerefTaggedPtr(int* ptr) {
*ptr = 1;
}
TEST(TopByteIgnoreTests, AddressTaggingGetSystemFeaturesAArch64) {
uint32_t features = 0;
ASSERT_OK(zx_system_get_features(ZX_FEATURE_KIND_ADDRESS_TAGGING, &features));
ASSERT_EQ(features, ZX_ARM64_FEATURE_ADDRESS_TAGGING_TBI);
// Since TBI is supported, we can access tagged pointers.
int val = 0;
DerefTaggedPtr(AddTag(&val, kTestTag));
ASSERT_EQ(val, 1);
}
using crash_function_t = void (*)(uintptr_t arg1, uintptr_t arg2);
// To test the crashing cases, we'll spawn a raw Zircon thread with no C
// library assistance so there are no hidden data structures to clean up after
// the thread is killed.
void CatchCrash(crash_function_t crash_function, uintptr_t arg1,
fit::function<void(zx::thread&)> before_start, zx_exception_report_t* report) {
zx::thread crash_thread;
constexpr std::string_view kThreadName = "Address tagging test thread";
ASSERT_OK(zx::thread::create(*zx::process::self(), kThreadName.data(), kThreadName.size(), 0,
&crash_thread));
zx::suspend_token suspend;
if (before_start) {
// This ensures the thread will be suspended after starting. This is needed
// for writing the thread state after it's running, but before we run anything
// in the entry point.
ASSERT_OK(crash_thread.suspend(&suspend));
ASSERT_TRUE(suspend);
}
// Set up to receive thread exceptions for the new thread.
zx::channel exception_channel;
ASSERT_OK(crash_thread.create_exception_channel(0, &exception_channel));
// Start it running with a stack and PC at the crash function's entry point.
constexpr size_t kThreadStackSize = ZIRCON_DEFAULT_STACK_SIZE;
std::unique_ptr<std::byte[]> crash_thread_stack = std::make_unique<std::byte[]>(kThreadStackSize);
const uintptr_t pc = reinterpret_cast<uintptr_t>(crash_function);
const uintptr_t sp = compute_initial_stack_pointer(
reinterpret_cast<uintptr_t>(crash_thread_stack.get()), kThreadStackSize);
ASSERT_OK(crash_thread.start(pc, sp, arg1, 0));
if (before_start) {
// The thread is now running, but it should be immediately suspended.
zx_signals_t observed;
ASSERT_OK(crash_thread.wait_one(ZX_THREAD_SUSPENDED, zx::time::infinite(), &observed));
ASSERT_NE(observed & ZX_THREAD_SUSPENDED, 0);
// Run any setup while the thread is suspended but before we dive into
// the function.
before_start(crash_thread);
// Resume the thread.
suspend.reset();
ASSERT_EQ(crash_thread.wait_one(ZX_THREAD_RUNNING, zx::time::infinite(), &observed), ZX_OK);
}
// Wait for the exception channel to be readable. This will happen when
// thread crashes and triggers the exception.
tu_channel_wait_readable(exception_channel.get());
// Get the FAR from the exception report.
ASSERT_OK(crash_thread.get_info(ZX_INFO_THREAD_EXCEPTION_REPORT, report, sizeof(*report), nullptr,
nullptr));
// Read the exception message.
zx::exception exc;
zx_exception_info_t exc_info;
uint32_t nbytes, nhandles;
ASSERT_OK(exception_channel.read(0, &exc_info, exc.reset_and_get_address(), sizeof(exc_info), 1,
&nbytes, &nhandles));
ASSERT_EQ(sizeof(exc_info), nbytes);
ASSERT_EQ(1, nhandles);
// We can also retrieve the FAR from the thread debug regs. Let's just make
// sure it's the same as what's in the exception report.
zx_thread_state_debug_regs_t regs = {};
ASSERT_OK(crash_thread.read_state(ZX_THREAD_STATE_DEBUG_REGS, &regs, sizeof(regs)));
ASSERT_EQ(report->context.arch.u.arm_64.far, regs.far);
// When the exception handle is closed (by the zx::exception destructor at
// the end of the function), the thread will resume from the exception. Set
// it up to "resume" by doing an immediate thread exit. This should make it
// safe to assume its stack will never be used again from here on out. (The
// stack will also be freed by a destructor at the end of the function.)
constexpr uint32_t kExceptionState = ZX_EXCEPTION_STATE_THREAD_EXIT;
ASSERT_OK(exc.set_property(ZX_PROP_EXCEPTION_STATE, &kExceptionState, sizeof(kExceptionState)));
}
#ifdef __clang__
[[clang::no_sanitize("all")]]
#endif
[[noreturn]] void
DerefTaggedPtrCrash(uintptr_t arg1, uintptr_t /*arg2*/) {
*(reinterpret_cast<int*>(arg1)) = 1;
__builtin_trap();
}
TEST(TopByteIgnoreTests, TaggedFARSegfault) {
// This is effectively a nullptr dereference.
uintptr_t tagged_ptr = AddTag(0, kTestTag);
zx_exception_report_t report = {};
ASSERT_NO_FATAL_FAILURE(
CatchCrash(DerefTaggedPtrCrash, tagged_ptr, /*before_start=*/nullptr, &report));
ASSERT_EQ(report.context.arch.u.arm_64.far, tagged_ptr);
}
int gVariableToChange = 0;
void SetupWatchpoint(zx::thread& crash_thread) {
zx_thread_state_debug_regs_t debug_regs = {};
// Turn on this HW watchpoint.
ARM64_DBGWCR_E_SET(&debug_regs.hw_wps[0].dbgwcr, 1);
// The BAS bits form an 8-bit mask that filters out matches on the aligned
// 8-byte address range indicated by the DBGWVR value based on the byte(s)
// accessed. So setting this to 0xff ensures that any kind of access to any
// of the 8 bytes will be trapped.
ARM64_DBGWCR_BAS_SET(&debug_regs.hw_wps[0].dbgwcr, 0xff);
// Only watch stores.
ARM64_DBGWCR_LSC_SET(&debug_regs.hw_wps[0].dbgwcr, 0b10);
// Use the untagged address. We should be able to compare against up to bit
// 55 when doing watchpoint address comparisons. The ARM spec also requires
// that bits 63:49 be a sign extension of bit 48 (that is, it cannot be tagged)
// (D13.3.12).
debug_regs.hw_wps[0].dbgwvr = reinterpret_cast<uintptr_t>(&gVariableToChange);
ASSERT_OK(crash_thread.write_state(ZX_THREAD_STATE_DEBUG_REGS, &debug_regs, sizeof(debug_regs)));
}
TEST(TopByteIgnoreTests, TaggedFARWatchpoint) {
uint64_t watched_addr = reinterpret_cast<uint64_t>(&gVariableToChange);
uintptr_t tagged_ptr = AddTag(watched_addr, kTestTag);
zx_exception_report_t report = {};
ASSERT_NO_FATAL_FAILURE(CatchCrash(DerefTaggedPtrCrash, tagged_ptr, SetupWatchpoint, &report));
EXPECT_EQ(report.header.type, ZX_EXCP_HW_BREAKPOINT);
EXPECT_EQ(report.context.arch.u.arm_64.far, tagged_ptr);
}
static zx_koid_t get_object_koid(zx_handle_t handle) {
zx_info_handle_basic_t info;
if (zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), NULL, NULL) != ZX_OK) {
return ZX_KOID_INVALID;
}
return info.koid;
}
void TestFutexWaitWake(uint8_t wait_tag, uint8_t wake_tag, uint8_t get_owner_tag) {
constexpr uint32_t kThreadWakeAllCount = std::numeric_limits<uint32_t>::max();
constexpr zx_futex_t kFutexVal = 1;
zx_futex_t futex = kFutexVal;
thrd_t thread;
struct ThreadArgs {
zx_futex_t* futex;
std::atomic<bool> about_to_wait;
zx_handle_t new_owner;
};
ThreadArgs thread_args{
AddTag(&futex, wait_tag),
false,
thrd_get_zx_handle(thrd_current()),
};
// Start a new thread that will wait until the current thread wakes the futex.
ASSERT_EQ(thrd_create(
&thread,
[](void* arg) -> int {
auto* thread_args = reinterpret_cast<ThreadArgs*>(arg);
thread_args->about_to_wait.store(true);
// Note that we pass in the futex value separately rather than derefing the futex
// pointer because, under ASan, a tagged futex pointer will cause some tag bits to
// spill into the rest of the pointer when calculating shadow memory if we do the
// dereference. This avoids us needing to do that.
zx_status_t status =
zx_futex_wait(thread_args->futex, kFutexVal, thread_args->new_owner,
zx_deadline_after(ZX_TIME_INFINITE));
return static_cast<int>(status);
},
&thread_args),
0);
// If something goes wrong and we bail out early, do our best to shut down as cleanly
auto cleanup = fit::defer([&]() {
EXPECT_OK(zx_futex_wake(&futex, kThreadWakeAllCount));
int result;
EXPECT_EQ(thrd_join(thread, &result), 0);
EXPECT_EQ(result, 0);
});
// Ensure that we're waiting on the futex before we wake it.
zx_info_thread_t info = {};
while (info.state != ZX_THREAD_STATE_BLOCKED_FUTEX) {
ASSERT_OK(zx_object_get_info(thrd_get_zx_handle(thread), ZX_INFO_THREAD, &info, sizeof(info),
nullptr, nullptr));
}
// Check the owner.
zx_koid_t owner;
EXPECT_OK(zx_futex_get_owner(AddTag(&futex, get_owner_tag), &owner));
EXPECT_EQ(owner, get_object_koid(thrd_get_zx_handle(thrd_current())));
}
TEST(TopByteIgnoreTests, FutexWaitWake) {
TestFutexWaitWake(0, 0, 0); // Wait and wake same futex on the same tag.
TestFutexWaitWake(kTestTag, kTestTag, kTestTag); // Wait and wake same futex on the same tag.
TestFutexWaitWake(kTestTag, kTestTag + 1,
kTestTag + 2); // Wait and wake same futex on different tags.
}
#ifdef __clang__
[[clang::no_sanitize("all")]]
#endif
uint8_t
UnsanitizedLoad(volatile uint8_t* ptr) {
return *ptr;
}
TEST(TopByteIgnoreTests, VmmPageFaultHandlerDataAbort) {
zx_handle_t root_vmar = zx_vmar_root_self();
// Create a new vmar to manage that we will eventually decommit.
zx_handle_t decommit_vmar;
uintptr_t addr;
ASSERT_OK(zx_vmar_allocate(root_vmar,
ZX_VM_CAN_MAP_SPECIFIC | ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE, 0,
zx_system_get_page_size() * 8, &decommit_vmar, &addr));
// Create a vmo we can write to.
zx_handle_t vmo;
ASSERT_OK(zx_vmo_create(zx_system_get_page_size(), 0, &vmo));
uint64_t mapping_addr;
ASSERT_OK(zx_vmar_map(decommit_vmar, ZX_VM_SPECIFIC | ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
zx_system_get_page_size(), vmo, 0, zx_system_get_page_size(),
&mapping_addr));
// We should be able to write normally.
*reinterpret_cast<volatile uint8_t*>(mapping_addr) = 42;
// After decommitting, the page is zero-filled. It will still be accessible, but not mapped to
// anything. This will result in a permission fault that will be handled successfully by the
// kernel's page fault handler. What we want to test for is that even if this pointer is tagged,
// then the kernel will still be able to handle this page fault successfully.
EXPECT_OK(zx_vmar_op_range(decommit_vmar, ZX_VMAR_OP_DECOMMIT, mapping_addr,
zx_system_get_page_size(), nullptr, 0));
mapping_addr = AddTag(mapping_addr, kTestTag);
// Do not do a regular dereference because ASan will right-shift the tag into the address bits
// then complain that this address doesn't have a corresponding shadow.
EXPECT_EQ(UnsanitizedLoad(reinterpret_cast<volatile uint8_t*>(mapping_addr)), 0);
}
arch::ArmExceptionSyndromeRegister::ExceptionClass GetEC(uint64_t esr) {
return arch::ArmExceptionSyndromeRegister::Get().FromValue(esr).ec();
}
// Making it global static ensures this is in rodata.
static constexpr uint32_t kUdf0 = 0;
TEST(TopByteIgnoreTests, InstructionAbortNoTag) {
// Unlike a data abort, instruction aborts on AArch64 will not include the tag in the FAR, so a
// tag will never reach the VM layer via an instruction abort. This test verifies the FAR does not
// include the tag in this case.
uintptr_t pc = AddTag(reinterpret_cast<uintptr_t>(&kUdf0), kTestTag);
zx_exception_report_t report = {};
ASSERT_NO_FATAL_FAILURE(CatchCrash(reinterpret_cast<crash_function_t>(pc), /*arg1=*/0,
/*before_start=*/nullptr, &report));
EXPECT_EQ(report.header.type, ZX_EXCP_FATAL_PAGE_FAULT);
ASSERT_EQ(GetEC(report.context.arch.u.arm_64.esr),
arch::ArmExceptionSyndromeRegister::ExceptionClass::kInstructionAbortLowerEl);
EXPECT_EQ(report.context.arch.u.arm_64.far, reinterpret_cast<uintptr_t>(&kUdf0));
}
#elif defined(__x86_64__)
TEST(TopByteIgnoreTests, AddressTaggingGetSystemFeaturesX86_64) {
uint32_t features = 0;
ASSERT_OK(zx_system_get_features(ZX_FEATURE_KIND_ADDRESS_TAGGING, &features));
ASSERT_EQ(features, 0);
}
#endif
} // namespace