| // 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 <fcntl.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); |
| } |