blob: 8fd3d7a43a9132b19b914c95320748a960ce5145 [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 <dirent.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <lib/zx/process.h>
#include <lib/zx/suspend_token.h>
#include <lib/zx/thread.h>
#include <lib/zx/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/port.h>
#include <test-utils/test-utils.h>
#include <zxtest/zxtest.h>
namespace {
const char* process_bin;
// SYSCALL_zx_channel_call_noretry is an internal system call used in the
// vDSO's implementation of zx_channel_call. It's not part of the ABI and
// so it's not exported from the vDSO. It's hard to test the kernel's
// invariants without calling this directly. So use some chicanery to
// find its address in the vDSO despite it not being public.
//
// The vdso-code.h header file is generated from the vDSO binary. It gives
// the offsets of the internal functions. So take a public vDSO function,
// subtract its offset to discover the vDSO base (could do this other ways,
// but this is the simplest), and then add the offset of the internal
// SYSCALL_zx_channel_call_noretry function we want to call.
#include "vdso-code.h"
zx_status_t zx_channel_call_noretry(zx_handle_t handle, uint32_t options, zx_time_t deadline,
const zx_channel_call_args_t* args, uint32_t* actual_bytes,
uint32_t* actual_handles) {
uintptr_t vdso_base = (uintptr_t)&zx_handle_close - VDSO_SYSCALL_zx_handle_close;
uintptr_t fnptr = vdso_base + VDSO_SYSCALL_zx_channel_call_noretry;
return (*(__typeof(zx_channel_call_noretry)*)fnptr)(handle, options, deadline, args, actual_bytes,
actual_handles);
}
// This runs in a separate process, since the expected outcome of running this
// function is that the process is shot by the kernel. It is launched by the
// bad_channel_call_contract_violation test.
void bad_channel_call() {
char msg[8] = {
0,
};
zx_channel_call_args_t args = {
.wr_bytes = msg,
.wr_handles = nullptr,
.rd_bytes = nullptr,
.rd_handles = nullptr,
.wr_num_bytes = sizeof(msg),
.wr_num_handles = 0,
.rd_num_bytes = 0,
.rd_num_handles = 0,
};
uint32_t act_bytes = UINT32_MAX;
uint32_t act_handles = UINT32_MAX;
zx::channel chan{zx_take_startup_handle(PA_HND(PA_USER0, 0))};
zx::event event{zx_take_startup_handle(PA_HND(PA_USER0, 1))};
// Send a copy of the thread handle to the parent, so the parent can suspend
// this thread.
zx::thread thread;
zx_status_t status = zx::thread::self()->duplicate(ZX_RIGHT_SAME_RIGHTS, &thread);
if (status != ZX_OK) {
event.signal(0, ZX_USER_SIGNAL_0);
__builtin_trap();
}
zx_handle_t handles[] = {thread.release()};
status = chan.write(0, nullptr, 0, handles, 1);
if (status != ZX_OK) {
event.signal(0, ZX_USER_SIGNAL_0);
__builtin_trap();
}
status =
zx_channel_call_noretry(chan.get(), 0, ZX_TIME_INFINITE, &args, &act_bytes, &act_handles);
if (status != ZX_ERR_INTERNAL_INTR_RETRY) {
event.signal(0, ZX_USER_SIGNAL_0);
__builtin_trap();
}
event.signal(0, ZX_USER_SIGNAL_1);
// Doing another channel call at this point violates the VDSO contract,
// since we haven't called SYSCALL_zx_channel_call_finish().
zx_channel_call_noretry(chan.get(), 0, ZX_TIME_INFINITE, &args, &act_bytes, &act_handles);
event.signal(0, ZX_USER_SIGNAL_0);
__builtin_trap();
}
// Verify that if an interrupted channel call does not retry and instead a new
// channel call happens, the process dies.
TEST(ChannelFatalTestCase, BadChannelCallContractViolation) {
zx::channel chan, remote;
zx::event event, event_copy;
ASSERT_OK(zx::channel::create(0, &chan, &remote));
ASSERT_OK(zx::event::create(0, &event));
ASSERT_OK(event.duplicate(ZX_RIGHT_SAME_RIGHTS, &event_copy));
const char* args[] = {
process_bin,
"child",
};
zx_handle_t handles[] = {
remote.release(),
event_copy.release(),
};
uint32_t handle_ids[] = {
PA_HND(PA_USER0, 0),
PA_HND(PA_USER0, 1),
};
int environ_count = 0;
for (char** i = environ; *i; i++) {
environ_count++;
}
zx::process proc(tu_launch_process(zx_job_default(), NULL, 2, args, environ_count, environ,
std::size(handles), handles, handle_ids));
uint32_t act_bytes = UINT32_MAX;
uint32_t act_handles = UINT32_MAX;
zx::thread thread;
// Get the thread handle from our child
ASSERT_OK(chan.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr));
ASSERT_OK(chan.read(0, nullptr, thread.reset_and_get_address(), 0, 1, &act_bytes, &act_handles));
ASSERT_EQ(act_handles, 1u);
// Wait for the channel call and pull its message out of the pipe. This
// relies on an implementation detail of suspend and channel_call,
// which is that once the syscall starts, suspend will not be acknowledged
// until it reaches the wait. So if we see the message written to the
// channel, we know the other thread is in the call, and so when we see
// it has suspended, it will have attempted the wait first.
EXPECT_OK(chan.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr));
char msg[8] = {0};
ASSERT_OK(chan.read(0, msg, nullptr, sizeof(msg), 0, &act_bytes, &act_handles));
{
zx::suspend_token suspend_token;
ASSERT_OK(thread.suspend(&suspend_token));
// Wait for the thread to suspend
zx_signals_t observed = 0u;
ASSERT_OK(thread.wait_one(ZX_THREAD_SUSPENDED, zx::time::infinite(), &observed));
// Resume the thread
}
// Wait for signal 0 or 1, meaning either it's going to try its second call,
// or something unexpected happened.
zx_signals_t observed = 0u;
ASSERT_OK(event.wait_one(ZX_USER_SIGNAL_0 | ZX_USER_SIGNAL_1, zx::time::infinite(), &observed));
ASSERT_TRUE(observed & ZX_USER_SIGNAL_1);
ASSERT_FALSE(observed & ZX_USER_SIGNAL_0);
// Process should have been shot
ASSERT_OK(proc.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr));
// Make sure we don't see the "unexpected thing happened" signal.
ASSERT_EQ(event.wait_one(ZX_USER_SIGNAL_0, zx::time(), &observed), ZX_ERR_TIMED_OUT);
}
} // namespace
int main(int argc, char** argv) {
process_bin = argv[0];
if (argc > 1 && !strcmp(argv[1], "child")) {
bad_channel_call();
return 0;
}
return RUN_ALL_TESTS(argc, argv);
}