| // Copyright 2016 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. |
| |
| // While not much will work if launchpad isn't already working, this test |
| // provides a place for testing aspects of launchpad that aren't necessarily |
| // normally used. |
| |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fit/defer.h> |
| #include <lib/zx/handle.h> |
| #include <lib/zx/vmo.h> |
| #include <limits.h> |
| #include <zircon/errors.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/types.h> |
| |
| #include <iterator> |
| |
| #include <elfload/elfload.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/array.h> |
| #include <launchpad/launchpad.h> |
| #include <launchpad/vmo.h> |
| #include <zxtest/zxtest.h> |
| |
| // argv[0] |
| static const char* program_path; |
| |
| #if __has_feature(address_sanitizer) |
| #if __has_feature(undefined_behavior_sanitizer) |
| #define LIBPREFIX "/boot/lib/asan-ubsan/" |
| #else |
| #define LIBPREFIX "/boot/lib/asan/" |
| #endif |
| #else |
| #define LIBPREFIX "/boot/lib/" |
| #endif |
| |
| static const char dynld_path[] = LIBPREFIX "ld.so.1"; |
| |
| static const char test_inferior_child_name[] = "inferior"; |
| |
| TEST(LaunchpadTest, Basic) { |
| launchpad_t* lp = NULL; |
| |
| zx_handle_t fdio_job = zx_job_default(); |
| ASSERT_NE(fdio_job, ZX_HANDLE_INVALID, "no fdio job object"); |
| |
| zx_handle_t job_copy = ZX_HANDLE_INVALID; |
| ASSERT_OK(zx_handle_duplicate(fdio_job, ZX_RIGHT_SAME_RIGHTS, &job_copy), |
| "zx_handle_duplicate failed"); |
| |
| zx_status_t status = launchpad_create(job_copy, test_inferior_child_name, &lp); |
| ASSERT_OK(status, "launchpad_create"); |
| |
| zx_handle_t vmo; |
| ASSERT_OK(launchpad_vmo_from_file(program_path, &vmo)); |
| status = launchpad_elf_load(lp, vmo); |
| ASSERT_OK(status, "launchpad_elf_load"); |
| |
| zx_vaddr_t base, entry; |
| status = launchpad_get_base_address(lp, &base); |
| ASSERT_OK(status, "launchpad_get_base_address"); |
| status = launchpad_get_entry_address(lp, &entry); |
| ASSERT_OK(status, "launchpad_get_entry_address"); |
| ASSERT_GT(base, 0u, "base > 0"); |
| |
| zx_handle_t dynld_vmo = ZX_HANDLE_INVALID; |
| ASSERT_OK(launchpad_vmo_from_file(dynld_path, &dynld_vmo)); |
| ASSERT_NE(dynld_vmo, ZX_HANDLE_INVALID, "launchpad_vmo_from_file"); |
| elf_load_header_t header; |
| uintptr_t phoff; |
| status = elf_load_prepare(dynld_vmo, NULL, 0, &header, &phoff); |
| ASSERT_OK(status, "elf_load_prepare"); |
| printf("entry %p, base %p, header entry %p\n", (void*)entry, (void*)base, (void*)header.e_entry); |
| ASSERT_EQ(entry, base + header.e_entry, "bad value for base or entry"); |
| zx_handle_close(dynld_vmo); |
| |
| launchpad_destroy(lp); |
| } |
| |
| void RunOneArgumentSizeTest(size_t size) { |
| launchpad_t* lp; |
| ASSERT_OK(launchpad_create(ZX_HANDLE_INVALID, "argument size test", &lp)); |
| |
| char* big = static_cast<char*>(malloc(size + 3)); |
| big[0] = ':'; |
| big[1] = ' '; |
| memset(&big[2], 'x', size); |
| big[2 + size] = '\0'; |
| const char* const argv[] = {"/boot/bin/sh", "-c", big}; |
| EXPECT_OK(launchpad_set_args(lp, std::size(argv), argv)); |
| free(big); |
| |
| EXPECT_OK(launchpad_load_from_file(lp, argv[0])); |
| |
| zx_handle_t proc = ZX_HANDLE_INVALID; |
| const char* errmsg = "???"; |
| EXPECT_OK(launchpad_go(lp, &proc, &errmsg), "%s", errmsg); |
| |
| EXPECT_OK(zx_object_wait_one(proc, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, NULL)); |
| zx_info_process_v2_t info; |
| EXPECT_OK(zx_object_get_info(proc, ZX_INFO_PROCESS_V2, &info, sizeof(info), NULL, NULL)); |
| EXPECT_OK(zx_handle_close(proc)); |
| |
| EXPECT_EQ(info.return_code, 0, "shell exit status"); |
| } |
| |
| TEST(LaunchPadTest, ArgumentSize) { |
| for (size_t size = 0; size < 2 * zx_system_get_page_size(); size += 1024) { |
| ASSERT_NO_FAILURES(RunOneArgumentSizeTest(size), "argument size is %-29zu", size); |
| } |
| } |
| |
| void RunWithArgsEnvHandles(unsigned int num_args, unsigned int num_env, unsigned int num_handles) { |
| launchpad_t* lp; |
| ASSERT_OK(launchpad_create(ZX_HANDLE_INVALID, "limits test", &lp)); |
| auto destroy_launchpad = fit::defer([&]() { launchpad_destroy(lp); }); |
| |
| // Set the args. |
| const unsigned int argc = 3 + num_args; |
| fbl::Array<const char*> argv(new const char*[argc], argc); |
| argv[0] = "/boot/bin/sh"; |
| argv[1] = "-c"; |
| argv[2] = ":"; |
| for (unsigned int i = 3; i < argc; ++i) { |
| argv[i] = "-v"; |
| } |
| ASSERT_OK(launchpad_set_args(lp, argc, argv.data()), "%s", launchpad_error_message(lp)); |
| ASSERT_OK(launchpad_load_from_file(lp, argv[0]), "%s", launchpad_error_message(lp)); |
| |
| // Set the env. |
| // |
| // Be sure to save room for a terminating null pointer. |
| num_env++; |
| fbl::Array<const char*> envp(new const char*[num_env], num_env); |
| for (unsigned int i = 0; i < num_env; ++i) { |
| envp[i] = "A=B"; |
| } |
| envp[num_env - 1] = NULL; |
| ASSERT_OK(launchpad_set_environ(lp, envp.data()), "%s", launchpad_error_message(lp)); |
| |
| // Set some handles. |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(0, 0, &vmo); |
| ASSERT_OK(status); |
| for (unsigned int i = 0; i < num_handles; ++i) { |
| zx::vmo vmo_dup; |
| zx_status_t status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup); |
| ASSERT_OK(status); |
| ASSERT_OK(launchpad_add_handle(lp, vmo_dup.release(), PA_HND(PA_USER0, i)), "%s", |
| launchpad_error_message(lp)); |
| } |
| |
| // Run it. |
| zx::handle proc; |
| const char* err = "unknown error"; |
| destroy_launchpad.cancel(); |
| ASSERT_OK(launchpad_go(lp, proc.reset_and_get_address(), &err), "%s", err); |
| |
| // See that it completed successfully. |
| ASSERT_OK(zx_object_wait_one(proc.get(), ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, NULL)); |
| zx_info_process_v2_t info; |
| ASSERT_OK(zx_object_get_info(proc.get(), ZX_INFO_PROCESS_V2, &info, sizeof(info), NULL, NULL)); |
| ASSERT_EQ(info.return_code, 0, "shell exit status"); |
| } |
| |
| TEST(LaunchpadTest, Limits) { |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(1, 1, 1)); |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(10000, 1, 1)); |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(1, 10000, 1)); |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(58, 58, 58)); |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(1, 1, 58)); |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(5000, 10000, 0)); |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(5000, 10000, 58)); |
| // This will allocate a stack that takes up almost a full page. |
| ASSERT_NO_FAILURES(RunWithArgsEnvHandles(1, 160, 0)); |
| } |
| |
| TEST(LaunchpadTest, ProcessCreateFailure) { |
| launchpad_t* lp; |
| EXPECT_STATUS(launchpad_create_with_jobs(ZX_HANDLE_INVALID, ZX_HANDLE_INVALID, "", &lp), |
| ZX_ERR_BAD_HANDLE); |
| EXPECT_STR_EQ(launchpad_error_message(lp), "create: zx_process_create() failed"); |
| launchpad_destroy(lp); |
| } |
| |
| // Providing our own main() because we want to pass argv[0] to one of the tests via program_path. |
| int main(int argc, char** argv) { |
| program_path = argv[0]; |
| return RUN_ALL_TESTS(argc, argv); |
| } |