blob: b5fac78a7b1771c59533b5c40a624afac3ae5470 [file] [log] [blame]
// Copyright 2023 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/fit/defer.h>
#include <lib/zx/job.h>
#include <lib/zx/resource.h>
#include <lib/zx/result.h>
#include <lib/zx/thread.h>
#include <lib/zx/vmo.h>
#include <zircon/syscalls-next.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <thread>
#include <zxtest/zxtest.h>
#include "helpers.h"
#include "write_to_stack_blob.h"
namespace {
void RunRestrictedMode(zx_vaddr_t cs_addr, zx_vaddr_t stack_addr,
std::optional<zx_excp_type_t> expected_exception, zx_status_t* result) {
// Set up the restricted state.
zx::vmo restricted;
*result = zx_restricted_bind_state(0, restricted.reset_and_get_address());
if (*result != ZX_OK) {
return;
}
zx_restricted_state_t state{};
#if defined(__x86_64__)
state.rsp = reinterpret_cast<uintptr_t>(stack_addr);
state.ip = reinterpret_cast<uintptr_t>(cs_addr);
#elif defined(__aarch64__) || defined(__riscv) // defined(__x86_64__)
state.sp = reinterpret_cast<uintptr_t>(stack_addr);
state.pc = reinterpret_cast<uintptr_t>(cs_addr);
#endif // defined(__arch64__) || defined(__riscv)
*result = restricted.write(&state, 0, sizeof(state));
if (*result != ZX_OK) {
return;
}
// Enter restricted mode.
zx_restricted_reason_t exit_reason = 99;
*result = restricted_enter_wrapper(0, &exit_reason);
if (*result != ZX_OK) {
return;
}
if (expected_exception) {
// If we expected an exception, check that we got that exception and return.
if (exit_reason != ZX_RESTRICTED_REASON_EXCEPTION) {
*result = ZX_ERR_INTERNAL;
return;
}
zx_restricted_exception_t exception_state{};
*result = restricted.read(&exception_state, 0, sizeof(exception_state));
if (*result != ZX_OK) {
return;
}
if (exception_state.exception.header.type != expected_exception) {
*result = ZX_ERR_INTERNAL;
}
return;
} else if (exit_reason != ZX_RESTRICTED_REASON_SYSCALL) {
// Otherwise, verify that we exited due to a syscall.
*result = ZX_ERR_INTERNAL;
return;
}
// Validate that the restricted mode routine wrote the right value to the stack without going
// through zx_vmo_read.
uint8_t expected[8] = {0xdd, 0xdd, 0xcc, 0xcc, 0xbb, 0xbb, 0xaa, 0xaa};
uint8_t* stack_values = reinterpret_cast<uint8_t*>(stack_addr - 8);
for (size_t i = 0; i < sizeof(expected); i++) {
if (expected[i] != stack_values[i]) {
*result = ZX_ERR_INTERNAL;
return;
}
}
}
} // namespace
TEST(RestrictedModeUnified, Basic) {
// Create the shared process that will run restricted mode.
static constexpr char kProcName[] = "restricted-shared-test";
zx::process restricted_proc;
zx::vmar restricted_vmar;
ASSERT_OK(zx_process_create_shared(zx_process_self(), 0, kProcName, sizeof(kProcName),
restricted_proc.reset_and_get_address(),
restricted_vmar.reset_and_get_address()));
// Set up the restricted mode stack.
zx::vmo stack;
const uint32_t stack_size = zx_system_get_page_size();
zx::result<zx_vaddr_t> res = SetupStack(restricted_vmar.get(), stack_size, &stack);
ASSERT_OK(res.status_value());
zx_vaddr_t stack_addr = res.value();
// Set up the restricted mode code segment.
res = SetupCodeSegment(restricted_vmar.get(), write_to_stack_blob());
ASSERT_OK(res.status_value());
zx_vaddr_t cs_addr = res.value();
// Spawn a thread inside the shared process that executes `RunRestrictedMode`. We want this
// thread to have all of the functionality provided by the C runtime (safe stack, libraries,
// etc.) but we don't want to have to do this ourselves. We accomplish this by setting the
// current thread's process handle to the shared process' handle, and then spawning a thread
// using C++ primitives.
auto prev_proc = thrd_set_zx_process(restricted_proc.get());
zx_status_t result = ZX_ERR_INTERNAL;
std::thread restricted_thread(RunRestrictedMode, cs_addr, stack_addr, std::nullopt, &result);
thrd_set_zx_process(prev_proc);
// Wait for the restricted thread to complete.
restricted_thread.join();
ASSERT_OK(result);
// Validate that the restricted mode routine wrote the right value to the stack using a
// zx_vmo_read.
uint8_t buffer[8];
ASSERT_OK(stack.read(buffer, stack_size - 8, 8));
uint8_t expected[8] = {0xdd, 0xdd, 0xcc, 0xcc, 0xbb, 0xbb, 0xaa, 0xaa};
for (size_t i = 0; i < sizeof(buffer); i++) {
EXPECT_EQ(expected[i], buffer[i]);
}
}
TEST(RestrictedModeUnified, TestRestrictedAccessToSharedRegion) {
// Create the shared process that will run restricted mode.
static constexpr char kProcName[] = "restricted-shared-test";
zx::process restricted_proc;
zx::vmar restricted_vmar;
ASSERT_OK(zx_process_create_shared(zx_process_self(), 0, kProcName, sizeof(kProcName),
restricted_proc.reset_and_get_address(),
restricted_vmar.reset_and_get_address()));
// Set up the restricted mode stack. Note that this stack is in the shared region, so
// we expect restricted mode to fail with a page fault exception.
zx::vmo stack;
const uint32_t stack_size = zx_system_get_page_size();
zx::result<zx_vaddr_t> res = SetupStack(zx::vmar::root_self()->get(), stack_size, &stack);
ASSERT_OK(res.status_value());
zx_vaddr_t stack_addr = res.value();
auto cleanup_stack = fit::defer([&stack_addr, &stack_size]() {
zx::vmar::root_self()->unmap(stack_addr - stack_size, stack_size);
});
// Set up the restricted mode code segment.
res = SetupCodeSegment(restricted_vmar.get(), write_to_stack_blob());
ASSERT_OK(res.status_value());
zx_vaddr_t cs_addr = res.value();
// Spawn a thread inside the shared process that executes `RunRestrictedMode`. We want this
// thread to have all of the functionality provided by the C runtime (safe stack, libraries,
// etc.) but we don't want to have to do this ourselves. We accomplish this by setting the
// current thread's process handle to the shared process' handle, and then spawning a thread
// using C++ primitives.
auto prev_proc = thrd_set_zx_process(restricted_proc.get());
zx_status_t result = ZX_ERR_INTERNAL;
std::thread restricted_thread(RunRestrictedMode, cs_addr, stack_addr,
std::optional<zx_excp_type_t>(ZX_EXCP_FATAL_PAGE_FAULT), &result);
thrd_set_zx_process(prev_proc);
// Wait for the restricted thread to complete.
restricted_thread.join();
ASSERT_OK(result);
}