blob: a47e7a118a275a0caa0e5426dcc01dd63d0acbb7 [file] [log] [blame]
// 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/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 <fbl/auto_call.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/"
#define LIBPREFIX "/boot/lib/asan/"
#define LIBPREFIX "/boot/lib/"
static const char dynld_path[] = LIBPREFIX "";
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");
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));
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);
zx_info_process_t info;
EXPECT_OK(zx_object_get_info(proc, ZX_INFO_PROCESS, &info, sizeof(info), NULL, NULL));
EXPECT_EQ(info.return_code, 0, "shell exit status");
TEST(LaunchPadTest, ArgumentSize) {
for (size_t size = 0; size < 2 * 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 = fbl::MakeAutoCall([&]() { 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,, "%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.
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,, "%s", launchpad_error_message(lp));
// Set some handles.
zx::vmo vmo;
zx_status_t status = zx::vmo::create(0, 0, &vmo);
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(launchpad_add_handle(lp, vmo_dup.release(), PA_HND(PA_USER0, i)), "%s",
// Run it.
zx::handle proc;
const char* err = "unknown error";
ASSERT_OK(launchpad_go(lp, proc.reset_and_get_address(), &err), "%s", err);
// See that it completed successfully.
zx_info_process_t info;
ASSERT_OK(zx_object_get_info(proc.get(), ZX_INFO_PROCESS, &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),
EXPECT_STR_EQ(launchpad_error_message(lp), "create: zx_process_create() failed");
// 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);