blob: 2ac51d04a903417805fba7763ed9ce86aa9090c7 [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/elfldltl/machine.h>
#include <lib/fit/defer.h>
#include <lib/zircon-internal/default_stack_size.h>
#include <lib/zx/exception.h>
#include <lib/zx/job.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/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
#include <zircon/threads.h>
#include <algorithm>
#include <mini-process/mini-process.h>
#include <zxtest/zxtest.h>
namespace {
#if defined(__aarch64__)
constexpr size_t kTagShift = 56;
constexpr uint8_t kTestTag = 0xAB;
constexpr size_t kThreadStackSize = ZIRCON_DEFAULT_STACK_SIZE;
constexpr uint64_t kTagMask = UINT64_C(0xff) << kTagShift;
// Add a tag to the pointer if the pointer is untagged. An optional tag value
// can be passed, and if one is, it will override the current tag.
//
// Under normal untagged use cases, this can be used for just adding an
// arbitrary tag value to a pointer. If hwasan is enabled, the pointer may
// already be tagged and will remain unchanged, unless a tag value is provided
// to override it. In that case, users of this function should be careful this
// doesn't lead to hwasan false-positives with tag-checking. Ideal cases where
// one might want to override the tag if hwasan is present are for ensuring
// that two pointers have different tags, since hwasan could technically but
// unlikely produce the same tag for different pointers.
constexpr uint64_t AddTagIfNeeded(uintptr_t ptr, uint8_t* newtag = nullptr) {
if (newtag) {
// Add the tag or overwrite it if there is one.
return (static_cast<uint64_t>(*newtag) << kTagShift) | (static_cast<uint64_t>(ptr) & ~kTagMask);
}
if (kTagMask & ptr) {
// There already exists a tag.
return ptr;
}
// Add the default test tag.
return (static_cast<uint64_t>(kTestTag) << kTagShift) | static_cast<uint64_t>(ptr);
}
template <typename T>
T* AddTagIfNeeded(T* ptr, uint8_t* newtag = nullptr) {
return reinterpret_cast<T*>(AddTagIfNeeded(reinterpret_cast<uintptr_t>(ptr), newtag));
}
constexpr uintptr_t RemoveTag(uintptr_t ptr) { return ptr & ~kTagMask; }
// 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(AddTagIfNeeded(&val));
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 = elfldltl::AbiTraits<>::InitialStackPointer(
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.
zx_signals_t pending;
ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
zx::time::infinite(), &pending));
ZX_ASSERT_MSG(pending & ZX_CHANNEL_READABLE, "exception channel peer closed");
// 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)));
}
TEST(TopByteIgnoreTests, VmarTaggedAddress) {
// Write pattern via VMO and read it via zx_process_read_memory(). Each address argument in these
// syscalls must not be tagged, but user pointers can be tagged.
uint8_t buff[] = {1, 2, 3, 4};
constexpr size_t kVmoSize = sizeof(buff);
constexpr size_t kVmarSize = 4096; // Must be page-aligned.
constexpr zx_vm_option_t kVmarOpts = ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE;
constexpr zx_vm_option_t kMapOpts = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
// Setup the VMO. User pointers provided to syscalls can be tagged and work properly.
zx::vmo vmo;
zx::vmar vmar;
zx_vaddr_t vmar_addr, map_addr;
ASSERT_OK(zx_vmo_create(kVmoSize, 0u, AddTagIfNeeded(vmo.reset_and_get_address())));
ASSERT_OK(zx_vmar_allocate(zx_vmar_root_self(), kVmarOpts, 0u, kVmarSize,
AddTagIfNeeded(vmar.reset_and_get_address()),
AddTagIfNeeded(&vmar_addr)));
ASSERT_OK(vmar.map(kMapOpts, 0u, vmo, 0u, kVmoSize, AddTagIfNeeded(&map_addr)));
// Note that the mapopts were set when mapping meaning this would be a no-op,
// but this just checks we can't tag vmar_protect regardless.
ASSERT_STATUS(vmar.protect(kMapOpts, AddTagIfNeeded(map_addr), kVmarSize), ZX_ERR_INVALID_ARGS);
ASSERT_OK(vmar.protect(kMapOpts, map_addr, kVmarSize));
auto IsUntagged = [](uintptr_t ptr) { return (ptr >> kTagShift) == 0; };
ASSERT_TRUE(IsUntagged(vmar_addr));
ASSERT_TRUE(IsUntagged(map_addr));
size_t actual = 0u;
// Write via the VMO...
ASSERT_OK(vmo.write(AddTagIfNeeded(buff), 0u, kVmoSize));
// ...then read via zx_process_read_memory. The kernel will treat a tagged vmar address normally,
// but fail when it sees there's no memory at the tagged address.
auto buf = std::make_unique<uint8_t[]>(kVmoSize);
ASSERT_STATUS(
zx::process::self()->read_memory(AddTagIfNeeded(vmar_addr), AddTagIfNeeded(buf.get()),
kVmoSize, AddTagIfNeeded(&actual)),
ZX_ERR_NOT_FOUND);
ASSERT_OK(zx::process::self()->read_memory(vmar_addr, AddTagIfNeeded(buf.get()), kVmoSize,
AddTagIfNeeded(&actual)));
ASSERT_EQ(actual, kVmoSize);
ASSERT_EQ(memcmp(buf.get(), buff, kVmoSize), 0);
// Shuffle the data that will be written.
std::reverse(buff, buff + kVmoSize);
// Now write via zx_process_write_memory...
ASSERT_STATUS(zx::process::self()->write_memory(AddTagIfNeeded(vmar_addr), AddTagIfNeeded(buff),
kVmoSize, AddTagIfNeeded(&actual)),
ZX_ERR_NOT_FOUND);
ASSERT_OK(zx::process::self()->write_memory(vmar_addr, AddTagIfNeeded(buff), kVmoSize,
AddTagIfNeeded(&actual)));
ASSERT_EQ(actual, kVmoSize);
// ...then read via the VMO.
ASSERT_OK(vmo.read(AddTagIfNeeded(buf.get()), 0u, kVmoSize));
ASSERT_EQ(memcmp(buf.get(), buff, kVmoSize), 0);
// We're done with the vmo and vmar. Although they will be destroyed after
// exiting this scope, we can do some checks here on syscalls for unmapping and
// decommitting.
ASSERT_STATUS(
vmar.op_range(ZX_VMAR_OP_DECOMMIT, AddTagIfNeeded(map_addr), kVmarSize, nullptr, 0u),
ZX_ERR_OUT_OF_RANGE);
ASSERT_OK(vmar.op_range(ZX_VMAR_OP_DECOMMIT, map_addr, kVmarSize, nullptr, 0u));
ASSERT_STATUS(vmar.unmap(AddTagIfNeeded(vmar_addr), kVmarSize), ZX_ERR_INVALID_ARGS);
ASSERT_OK(vmar.unmap(vmar_addr, kVmarSize));
ASSERT_OK(vmar.destroy());
}
#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 = AddTagIfNeeded(0);
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 = AddTagIfNeeded(watched_addr);
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{
// Manually add the tag here which may be different from the one in the pointer passed to
// `zx_futex_get_owner`. This tag should be irrelevant on a futex comparison.
AddTagIfNeeded(&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;
// Manually add the tag here which may be different from the one in the initial `thread_args`.
// This tag should be irrelevant on a futex comparison.
EXPECT_OK(zx_futex_get_owner(AddTagIfNeeded(&futex, &get_owner_tag), &owner));
EXPECT_EQ(owner, get_object_koid(thrd_get_zx_handle(thrd_current())));
}
TEST(TopByteIgnoreTests, FutexWaitWake) {
// These tags are manually included in futex pointers passed to futex syscalls. The actual tag
// values don't matter as long as we can test they work as intended if they're the same or
// different.
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 = AddTagIfNeeded(mapping_addr);
// 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);
ASSERT_OK(zx_vmar_destroy(decommit_vmar));
}
arch::ArmExceptionSyndromeRegister::ExceptionClass GetEC(uint64_t esr) {
return arch::ArmExceptionSyndromeRegister::Get().FromValue(esr).ec();
}
// Making it global static ensures this is in rodata.
const 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 = AddTagIfNeeded(reinterpret_cast<uintptr_t>(&kUdf0));
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, RemoveTag(reinterpret_cast<uintptr_t>(&kUdf0)));
}
#ifdef __clang__
[[clang::no_sanitize("all")]]
#endif
__NO_RETURN void
DoNothing() {
zx_thread_exit();
}
TEST(TopByteIgnoreTests, ThreadStartTaggedAddress) {
std::unique_ptr<std::byte[]> thread_stack = std::make_unique<std::byte[]>(kThreadStackSize);
const uintptr_t pc = reinterpret_cast<uintptr_t>(DoNothing);
const uintptr_t sp = elfldltl::AbiTraits<>::InitialStackPointer(
reinterpret_cast<uintptr_t>(thread_stack.get()), kThreadStackSize);
auto run_thread = [](uintptr_t pc, uintptr_t sp) {
constexpr std::string_view kThreadName = "TBI tagged entry/stack";
zx::thread thread;
ASSERT_OK(zx::thread::create(*zx::process::self(), kThreadName.data(), kThreadName.size(), 0,
&thread));
ASSERT_OK(thread.start(pc, sp, 0, 0));
zx_signals_t observed;
ASSERT_OK(
thread.wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), AddTagIfNeeded(&observed)));
ASSERT_TRUE(observed & ZX_THREAD_TERMINATED);
};
// Both the PC and SP can be tagged.
run_thread(AddTagIfNeeded(pc), sp);
run_thread(pc, AddTagIfNeeded(sp));
}
TEST(TopByteIgnoreTests, ProcessStartTaggedAddress) {
auto run_process = [](uint8_t pc_tag, uint8_t sp_tag) {
zx::process proc;
zx::thread thread;
zx::vmar vmar;
constexpr std::string_view kTestName = "TBI process";
ASSERT_OK(zx::process::create(*zx::job::default_job(), kTestName.data(), kTestName.size(), 0,
&proc, &vmar));
ASSERT_OK(zx::thread::create(proc, kTestName.data(), kTestName.size(), 0, &thread));
// The process will get no handles, but it can still make syscalls.
// The vDSO's e_entry points to zx_process_exit. So the process will
// enter at `zx_process_exit(ZX_HANDLE_INVALID);`.
uintptr_t entry;
EXPECT_OK(mini_process_load_vdso(proc.get(), vmar.get(), nullptr, &entry));
// The vDSO ABI needs a stack, though zx_process_exit actually might not.
uintptr_t stack_base, sp;
EXPECT_OK(mini_process_load_stack(vmar.get(), false, &stack_base, &sp));
zx_handle_close(vmar.get());
ASSERT_OK(proc.start(thread, AddTagIfNeeded(entry, &pc_tag), AddTagIfNeeded(sp, &sp_tag),
zx::handle(), 0));
zx_signals_t signals;
EXPECT_OK(proc.wait_one(ZX_TASK_TERMINATED, zx::deadline_after(zx::sec(1)), &signals));
EXPECT_EQ(signals, ZX_TASK_TERMINATED);
};
run_process(kTestTag, 0);
run_process(0, kTestTag);
}
#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