blob: 2380b7398ef5889f039289cdcece47029532b2e3 [file] [log] [blame]
// Copyright 2018 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/fzl/memory-probe.h>
#include <limits.h>
#include <stdio.h>
#include <zircon/assert.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/port.h>
#include <lib/zx/port.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
namespace {
enum class ProbeOperation {
kRead,
kWrite
};
#if __has_feature(address_sanitizer)
[[clang::no_sanitize("address")]]
#endif
void except_thread_func(uintptr_t op, uintptr_t address) {
volatile char* ch_address = reinterpret_cast<char*>(address);
char ch = *ch_address;
if (static_cast<ProbeOperation>(op) == ProbeOperation::kWrite)
*ch_address = ch;
zx_thread_exit();
}
bool do_probe(ProbeOperation op, const void* addr) {
// This function starts a new thread to perform the read/write test, and catches any exceptions
// in this thread to see if it failed or not.
zx::thread thread;
zx_status_t status = zx::thread::create(*zx::process::self(), "memory_probe", 12u, 0u, &thread);
if (status != ZX_OK)
return false;
alignas(16) static uint8_t thread_stack[128];
void* stack = thread_stack + sizeof(thread_stack);
zx::port port;
status = zx::port::create(0, &port);
if (status != ZX_OK)
return false;
// Cause the port to be signaled with kThreadKey when the background thread crashes or teminates without crashing.
constexpr uint64_t kThreadKey = 0x42;
if (thread.wait_async(port, kThreadKey, ZX_THREAD_TERMINATED, ZX_WAIT_ASYNC_ONCE) != ZX_OK) {
return false;
}
if (zx_task_bind_exception_port(thread.get(), port.get(), kThreadKey, 0) != ZX_OK) {
return false;
}
thread.start(&except_thread_func, stack, static_cast<uintptr_t>(op), reinterpret_cast<uintptr_t>(addr));
// Wait for crash or thread completion.
zx_port_packet_t packet;
if (port.wait(zx::time::infinite(), &packet) == ZX_OK) {
if (ZX_PKT_IS_EXCEPTION(packet.type)) {
// Thread crashed so the operation failed. The thread is now in a suspended state and
// needs to be explicitly terminated.
thread.kill();
// Wait for the kill operation to complete so we don't
// interrupt exception handling by closing port.
// TODO(ZX-4105): Reexamine once killing and exceptions have defined interactions.
zx_status_t status = port.wait(zx::time::infinite(), &packet);
ZX_DEBUG_ASSERT(status == ZX_OK
&& ZX_PKT_IS_SIGNAL_ONE(packet.type)
&& packet.key == kThreadKey
&& (packet.signal.observed & ZX_THREAD_TERMINATED));
return false;
}
if (ZX_PKT_IS_SIGNAL_ONE(packet.type)) {
if (packet.key == kThreadKey && (packet.signal.observed & ZX_THREAD_TERMINATED)) {
// Thread terminated normally so the memory is readable/writable.
return true;
}
}
}
return false;
}
} // namespace
bool probe_for_read(const void* addr) {
return do_probe(ProbeOperation::kRead, addr);
}
bool probe_for_write(void* addr) {
return do_probe(ProbeOperation::kWrite, addr);
}