blob: f75f5de5a43654ec6f2333a74764e6ce7f0d3108 [file] [log] [blame]
// Copyright 2017 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 <assert.h>
#include <stdio.h>
#include <threads.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <lib/zx/exception.h>
#include <lib/zx/handle.h>
#include <lib/zx/job.h>
#include <lib/zx/port.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <lib/zx/vmar.h>
#include <fbl/algorithm.h>
#include <zircon/process.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/policy.h>
#include <zircon/syscalls/port.h>
#include <mini-process/mini-process.h>
#include <unistd.h>
#include <unittest/unittest.h>
static const unsigned kExceptionPortKey = 42u;
// Basic job operation is tested by core-tests.
static zx::job make_job() {
zx::job job;
if (zx::job::create(*zx::job::default_job(), 0u, &job) != ZX_OK)
return zx::job();
return job;
}
static zx::process make_test_process(const zx::job& job, zx::thread* out_thread,
zx_handle_t* ctrl) {
zx::vmar vmar;
zx::process proc;
zx_status_t status = zx::process::create(job, "poltst", 6u, 0u, &proc, &vmar);
if (status != ZX_OK)
return zx::process();
zx::thread thread;
status = zx::thread::create(proc, "poltst", 6u, 0, &thread);
if (status != ZX_OK)
return zx::process();
if (out_thread) {
status = thread.duplicate(ZX_RIGHT_SAME_RIGHTS, out_thread);
if (status != ZX_OK)
return zx::process();
}
zx::event event;
status = zx::event::create(0u, &event);
if (status != ZX_OK)
return zx::process();
auto thr = thread.release();
status = start_mini_process_etc(proc.get(), thr, vmar.get(), event.release(), ctrl);
if (status != ZX_OK)
return zx::process();
return proc;
}
static bool AbsThenRel() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_BAD_HANDLE, ZX_POL_ACTION_KILL}};
auto job = make_job();
EXPECT_EQ(job.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC, policy,
static_cast<uint32_t>(fbl::count_of(policy))),
ZX_OK);
// A contradictory policy should fail.
policy[0].policy = ZX_POL_ACTION_EXCEPTION | ZX_POL_ACTION_DENY;
EXPECT_EQ(job.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC, policy,
static_cast<uint32_t>(fbl::count_of(policy))),
ZX_ERR_ALREADY_EXISTS);
// The same again will succeed.
policy[0].policy = ZX_POL_ACTION_KILL;
EXPECT_EQ(job.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC, policy,
static_cast<uint32_t>(fbl::count_of(policy))),
ZX_OK);
// A contradictory relative policy will succeed, but is a no-op
policy[0].policy = ZX_POL_ACTION_ALLOW;
EXPECT_EQ(job.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC, policy,
static_cast<uint32_t>(fbl::count_of(policy))),
ZX_OK);
zx_policy_basic_t more[] = {{ZX_POL_NEW_CHANNEL, ZX_POL_ACTION_ALLOW | ZX_POL_ACTION_EXCEPTION},
{ZX_POL_NEW_FIFO, ZX_POL_ACTION_DENY}};
// An additional absolute policy that doesn't contradict existing
// policy can be added.
EXPECT_EQ(job.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC, more,
static_cast<uint32_t>(fbl::count_of(more))),
ZX_OK);
END_TEST;
}
__WARN_UNUSED_RESULT
static bool invalid_calls(uint32_t options) {
BEGIN_HELPER;
auto job = make_job();
EXPECT_EQ(job.set_policy(options, ZX_JOB_POL_BASIC, nullptr, 0u), ZX_ERR_INVALID_ARGS);
EXPECT_EQ(job.set_policy(options, ZX_JOB_POL_BASIC, nullptr, 5u), ZX_ERR_INVALID_ARGS);
zx_policy_basic_t policy1[] = {
{ZX_POL_BAD_HANDLE, ZX_POL_ACTION_KILL},
};
EXPECT_EQ(job.set_policy(options, ZX_JOB_POL_BASIC, policy1, 0u), ZX_ERR_INVALID_ARGS);
zx_policy_basic_t policy2[] = {
{100001u, ZX_POL_ACTION_KILL},
};
EXPECT_EQ(job.set_policy(options, ZX_JOB_POL_BASIC, policy2,
static_cast<uint32_t>(fbl::count_of(policy2))),
ZX_ERR_INVALID_ARGS);
zx_policy_basic_t policy3[] = {
{ZX_POL_BAD_HANDLE, 100001u},
};
EXPECT_EQ(job.set_policy(options, ZX_JOB_POL_BASIC, policy3,
static_cast<uint32_t>(fbl::count_of(policy2))),
ZX_ERR_NOT_SUPPORTED);
// The job will still accept a valid combination:
zx_policy_basic_t policy4[] = {{ZX_POL_BAD_HANDLE, ZX_POL_ACTION_KILL}};
EXPECT_EQ(job.set_policy(options, ZX_JOB_POL_BASIC, policy4,
static_cast<uint32_t>(fbl::count_of(policy4))),
ZX_OK);
END_HELPER;
}
static bool InvalidCallsAbs() {
BEGIN_TEST;
ASSERT_TRUE(invalid_calls(ZX_JOB_POL_ABSOLUTE));
END_TEST;
}
static bool InvalidCallsRel() {
BEGIN_TEST;
ASSERT_TRUE(invalid_calls(ZX_JOB_POL_RELATIVE));
END_TEST;
}
// Checks that executing the given mini-process.h command (|minip_cmd|) produces the given result
// (|expect|) when the given policy is in force.
//
// Returns true if all expectations are met.
__WARN_UNUSED_RESULT
static bool CheckInvokingPolicy(zx_policy_basic_t* pol, uint32_t pol_count, uint32_t minip_cmd,
zx_status_t expect) {
BEGIN_HELPER;
auto job = make_job();
ASSERT_EQ(job.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC, pol, pol_count), ZX_OK);
zx_handle_t ctrl;
auto proc = make_test_process(job, nullptr, &ctrl);
ASSERT_TRUE(proc.is_valid());
ASSERT_NE(ctrl, ZX_HANDLE_INVALID);
zx_handle_t obj;
EXPECT_EQ(mini_process_cmd(ctrl, minip_cmd, &obj), expect);
EXPECT_EQ(mini_process_cmd(ctrl, MINIP_CMD_EXIT_NORMAL, nullptr), ZX_ERR_PEER_CLOSED);
zx_handle_close(ctrl);
END_HELPER;
}
static bool EnforceDenyEvent() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_EVENT, ZX_POL_ACTION_DENY}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_EVENT, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool EnforceDenyProfile() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_PROFILE, ZX_POL_ACTION_DENY}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_PROFILE, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool EnforceDenyChannel() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_CHANNEL, ZX_POL_ACTION_DENY}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_CHANNEL, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool EnforceDenyPagerVmo() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_VMO, ZX_POL_ACTION_DENY}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_PAGER_VMO, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool EnforceDenyVmoContiguous() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_VMO, ZX_POL_ACTION_DENY}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_VMO_CONTIGUOUS, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool EnforceDenyVmoPhysical() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_VMO, ZX_POL_ACTION_DENY}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_VMO_PHYSICAL, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool EnforceDenyAny() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_EVENT, ZX_ERR_ACCESS_DENIED));
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_PROFILE, ZX_ERR_ACCESS_DENIED));
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_CHANNEL, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool EnforceAllowAny() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_ANY, ZX_POL_ACTION_ALLOW}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_EVENT, ZX_OK));
END_TEST;
}
static bool EnforceDenyButEvent() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {{ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY},
{ZX_POL_NEW_EVENT, ZX_POL_ACTION_ALLOW}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_EVENT, ZX_OK));
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_CHANNEL, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
__WARN_UNUSED_RESULT
static bool get_koid(zx_handle_t handle, zx_koid_t* koid) {
BEGIN_HELPER;
zx_info_handle_basic_t info;
ASSERT_EQ(
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr),
ZX_OK);
*koid = info.koid;
END_HELPER;
}
#if defined(__x86_64__)
static uint64_t get_syscall_result(zx_thread_state_general_regs_t* regs) {
return regs->rax;
}
#elif defined(__aarch64__)
static uint64_t get_syscall_result(zx_thread_state_general_regs_t* regs) {
return regs->r[0];
}
#else
#error Unsupported architecture
#endif
namespace {
enum class ExceptionTestType { kPorts,
kChannels };
} // namespace
// Like CheckInvokingPolicy(), this tests that executing the given
// mini-process.h command produces the given result when the given policy
// is in force. In addition, it tests that a debug port exception gets
// generated.
__WARN_UNUSED_RESULT
static bool CheckInvokingPolicyWithException(ExceptionTestType test_type,
const zx_policy_basic_t* policy, uint32_t policy_count,
uint32_t minip_cmd,
zx_status_t expected_syscall_result) {
BEGIN_HELPER;
auto job = make_job();
ASSERT_EQ(job.set_policy(ZX_JOB_POL_ABSOLUTE, ZX_JOB_POL_BASIC, policy, policy_count), ZX_OK);
zx_handle_t ctrl;
zx::thread thread;
auto proc = make_test_process(job, &thread, &ctrl);
ASSERT_TRUE(proc.is_valid());
ASSERT_NE(ctrl, ZX_HANDLE_INVALID);
zx_handle_t exc_port = ZX_HANDLE_INVALID;
zx::channel exc_channel;
if (test_type == ExceptionTestType::kPorts) {
ASSERT_EQ(zx_port_create(0, &exc_port), ZX_OK);
ASSERT_EQ(zx_task_bind_exception_port(proc.get(), exc_port, kExceptionPortKey,
ZX_EXCEPTION_PORT_DEBUGGER),
ZX_OK);
} else {
ASSERT_EQ(proc.create_exception_channel(ZX_EXCEPTION_PORT_DEBUGGER, &exc_channel), ZX_OK);
}
EXPECT_EQ(mini_process_cmd_send(ctrl, minip_cmd), ZX_OK);
// Check that the subprocess did not return a reply yet (indicating
// that it was suspended).
EXPECT_EQ(zx_object_wait_one(ctrl, ZX_CHANNEL_READABLE, zx_deadline_after(ZX_MSEC(1)), nullptr),
ZX_ERR_TIMED_OUT);
zx_koid_t pid;
zx_koid_t tid;
ASSERT_TRUE(get_koid(proc.get(), &pid));
ASSERT_TRUE(get_koid(thread.get(), &tid));
// Check that we receive an exception message.
zx::exception exception;
if (test_type == ExceptionTestType::kPorts) {
zx_port_packet_t packet;
ASSERT_EQ(zx_port_wait(exc_port, ZX_TIME_INFINITE, &packet), ZX_OK);
// Check the exception message contents.
ASSERT_EQ(packet.key, kExceptionPortKey);
ASSERT_EQ(packet.type, (uint32_t)ZX_EXCP_POLICY_ERROR);
ASSERT_EQ(packet.exception.pid, pid);
ASSERT_EQ(packet.exception.tid, tid);
} else {
zx_exception_info_t info;
ASSERT_EQ(exc_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr), ZX_OK);
ASSERT_EQ(exc_channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1,
nullptr, nullptr),
ZX_OK);
ASSERT_EQ(info.type, ZX_EXCP_POLICY_ERROR);
ASSERT_EQ(info.tid, tid);
ASSERT_EQ(info.pid, pid);
// Make sure the exception has the correct task handles.
zx::thread exception_thread;
zx::process exception_process;
ASSERT_EQ(exception.get_thread(&exception_thread), ZX_OK);
ASSERT_EQ(exception.get_process(&exception_process), ZX_OK);
zx_koid_t handle_tid = ZX_KOID_INVALID;
EXPECT_TRUE(get_koid(exception_thread.get(), &handle_tid));
EXPECT_EQ(handle_tid, tid);
zx_koid_t handle_pid = ZX_KOID_INVALID;
EXPECT_TRUE(get_koid(exception_process.get(), &handle_pid));
EXPECT_EQ(handle_pid, pid);
}
// Check that we can read the thread's register state.
zx_thread_state_general_regs_t regs;
ASSERT_EQ(zx_thread_read_state(thread.get(), ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs)),
ZX_OK);
ASSERT_EQ(get_syscall_result(&regs), (uint64_t)expected_syscall_result);
// TODO(mseaborn): Check the values of other registers. We could check
// that rip/pc is within the VDSO, which will require figuring out
// where the VDSO is mapped. We could check that unwinding the stack
// using crashlogger gives a correct backtrace.
// Resume the thread.
if (test_type == ExceptionTestType::kPorts) {
ASSERT_EQ(zx_task_resume_from_exception(thread.get(), exc_port, 0), ZX_OK);
} else {
uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
ASSERT_EQ(exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)), ZX_OK);
exception.reset();
}
// Check that the read-ready state of the channel changed compared with
// the earlier check.
EXPECT_EQ(zx_object_wait_one(ctrl, ZX_CHANNEL_READABLE, ZX_TIME_INFINITE, nullptr), ZX_OK);
// Check that we receive a reply message from the resumed thread.
zx_handle_t obj;
EXPECT_EQ(mini_process_cmd_read_reply(ctrl, &obj), expected_syscall_result);
if (expected_syscall_result == ZX_OK)
EXPECT_EQ(zx_handle_close(obj), ZX_OK);
// Clean up: Tell the subprocess to exit.
EXPECT_EQ(mini_process_cmd(ctrl, MINIP_CMD_EXIT_NORMAL, nullptr), ZX_ERR_PEER_CLOSED);
zx_handle_close(ctrl);
END_HELPER;
}
// Invokes a policy exception test using both port and channel exceptions.
__WARN_UNUSED_RESULT
static bool CheckInvokingPolicyWithException(const zx_policy_basic_t* policy, uint32_t policy_count,
uint32_t minip_cmd,
zx_status_t expected_syscall_result) {
BEGIN_HELPER;
EXPECT_TRUE(CheckInvokingPolicyWithException(ExceptionTestType::kPorts, policy, policy_count,
minip_cmd, expected_syscall_result));
EXPECT_TRUE(CheckInvokingPolicyWithException(ExceptionTestType::kChannels, policy, policy_count,
minip_cmd, expected_syscall_result));
END_HELPER;
}
static bool TestExceptionOnNewEventAndDeny() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {
{ZX_POL_NEW_EVENT, ZX_POL_ACTION_DENY | ZX_POL_ACTION_EXCEPTION},
};
ASSERT_TRUE(CheckInvokingPolicyWithException(policy,
static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_EVENT, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
static bool TestExceptionOnNewEventButAllow() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {
{ZX_POL_NEW_EVENT, ZX_POL_ACTION_ALLOW | ZX_POL_ACTION_EXCEPTION},
};
ASSERT_TRUE(CheckInvokingPolicyWithException(
policy, static_cast<uint32_t>(fbl::count_of(policy)), MINIP_CMD_CREATE_EVENT, ZX_OK));
END_TEST;
}
static bool TestExceptionOnNewProfileAndDeny() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {
{ZX_POL_NEW_PROFILE, ZX_POL_ACTION_DENY | ZX_POL_ACTION_EXCEPTION},
};
ASSERT_TRUE(CheckInvokingPolicyWithException(policy,
static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_CREATE_PROFILE, ZX_ERR_ACCESS_DENIED));
END_TEST;
}
// Test ZX_POL_BAD_HANDLE when syscalls are allowed to continue.
static bool TestErrorOnBadHandle() {
BEGIN_TEST;
// The ALLOW and DENY actions should be equivalent for ZX_POL_BAD_HANDLE.
uint32_t actions[] = {ZX_POL_ACTION_ALLOW, ZX_POL_ACTION_DENY};
for (uint32_t action : actions) {
unittest_printf_critical("Testing action=%d\n", action);
zx_policy_basic_t policy[] = {
{ZX_POL_BAD_HANDLE, action},
};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_USE_BAD_HANDLE_CLOSED, ZX_ERR_BAD_HANDLE));
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_USE_BAD_HANDLE_TRANSFERRED, ZX_ERR_BAD_HANDLE));
}
END_TEST;
}
// Test ZX_POL_BAD_HANDLE with ZX_POL_ACTION_EXCEPTION.
static bool TestExceptionOnBadHandle() {
BEGIN_TEST;
// The ALLOW and DENY actions should be equivalent for ZX_POL_BAD_HANDLE.
uint32_t actions[] = {ZX_POL_ACTION_ALLOW, ZX_POL_ACTION_DENY};
for (uint32_t action : actions) {
unittest_printf_critical("Testing action=%d\n", action);
zx_policy_basic_t policy[] = {
{ZX_POL_BAD_HANDLE, action | ZX_POL_ACTION_EXCEPTION},
};
ASSERT_TRUE(
CheckInvokingPolicyWithException(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_USE_BAD_HANDLE_CLOSED, ZX_ERR_BAD_HANDLE));
ASSERT_TRUE(CheckInvokingPolicyWithException(
policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_USE_BAD_HANDLE_TRANSFERRED, ZX_ERR_BAD_HANDLE));
}
END_TEST;
}
// The one exception for ZX_POL_BAD_HANDLE is zx_object_info( ZX_INFO_HANDLE_VALID).
static bool TestGetInfoOnBadHandle() {
BEGIN_TEST;
zx_policy_basic_t policy[] = {
{ZX_POL_BAD_HANDLE, ZX_POL_ACTION_DENY | ZX_POL_ACTION_EXCEPTION}};
ASSERT_TRUE(CheckInvokingPolicy(policy, static_cast<uint32_t>(fbl::count_of(policy)),
MINIP_CMD_VALIDATE_CLOSED_HANDLE, ZX_ERR_BAD_HANDLE));
END_TEST;
}
BEGIN_TEST_CASE(job_policy)
RUN_TEST(InvalidCallsAbs)
RUN_TEST(InvalidCallsRel)
RUN_TEST(AbsThenRel)
RUN_TEST(EnforceDenyEvent)
RUN_TEST(EnforceDenyProfile)
RUN_TEST(EnforceDenyChannel)
RUN_TEST(EnforceDenyPagerVmo)
RUN_TEST(EnforceDenyVmoContiguous)
RUN_TEST(EnforceDenyVmoPhysical)
RUN_TEST(EnforceDenyAny)
RUN_TEST(EnforceAllowAny)
RUN_TEST(EnforceDenyButEvent)
RUN_TEST(TestExceptionOnNewEventAndDeny)
RUN_TEST(TestExceptionOnNewEventButAllow)
RUN_TEST(TestExceptionOnNewProfileAndDeny)
RUN_TEST(TestErrorOnBadHandle)
RUN_TEST(TestExceptionOnBadHandle)
RUN_TEST(TestGetInfoOnBadHandle)
END_TEST_CASE(job_policy)