blob: d5fe348efce6647e623c28977c0dd4d8b1e91834 [file] [log] [blame]
// Copyright 2018 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 <dlfcn.h>
#include <fcntl.h>
#include <fuchsia/boot/c/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/namespace.h>
#include <lib/zx/channel.h>
#include <lib/zx/debuglog.h>
#include <lib/zx/job.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <zircon/boot/image.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls/system.h>
#include <cstdint>
#include <string>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/string.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <zxtest/zxtest.h>
#include "../util.h"
namespace {
namespace fio = fuchsia_io;
static fbl::Vector<fbl::String> arguments;
constexpr char kFactoryItemsPath[] = "/svc/" fuchsia_boot_FactoryItems_Name;
constexpr char kItemsPath[] = "/svc/" fuchsia_boot_Items_Name;
void print_test_success_string() {
// Get the debuglog handle.
// If any of these operations fail, there's nothing we can really do here, so
// just move along.
zx::debuglog log;
zx_status_t status = zx::debuglog::create(zx::resource(), 0, &log);
if (status != ZX_OK) {
return;
}
// print the success string to the debug log
zx_debuglog_write(log.get(), 0, ZBI_TEST_SUCCESS_STRING, sizeof(ZBI_TEST_SUCCESS_STRING));
}
// Consumes the given fd and returns the result of a call to fuchsia.io.Node.NodeGetFlags.
uint32_t fd_get_flags(fbl::unique_fd fd) {
zx::channel file_channel;
if (fdio_fd_transfer(fd.release(), file_channel.reset_and_get_address()) != ZX_OK) {
return 0;
}
fidl::WireSyncClient<fio::Node> client(std::move(file_channel));
auto result = client.NodeGetFlags();
if (result.status() != ZX_OK) {
return 0;
}
fidl::WireResponse<fio::Node::NodeGetFlags>* response = result.Unwrap();
if (response->s != ZX_OK) {
return 0;
}
return response->flags;
}
zx_rights_t get_rights(const zx::object_base& handle) {
zx_info_handle_basic_t info;
zx_status_t status = handle.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.rights : ZX_RIGHT_NONE;
}
// Make sure the loader works
TEST(BootsvcIntegrationTest, Loader) {
// Request loading a library we don't use
void* ptr = dlopen("libdriver.so", RTLD_LAZY | RTLD_LOCAL);
ASSERT_NOT_NULL(ptr);
dlclose(ptr);
}
// Make sure that bootsvc gave us a namespace with only /boot and /svc.
TEST(BootsvcIntegrationTest, Namespace) {
fdio_flat_namespace_t* ns;
ASSERT_EQ(fdio_ns_export_root(&ns), ZX_OK);
// Close the cloned handles, since we don't need them
for (size_t i = 0; i < ns->count; ++i) {
zx_handle_close(ns->handle[i]);
}
ASSERT_EQ(ns->count, 2);
EXPECT_STR_EQ(ns->path[0], "/boot");
EXPECT_STR_EQ(ns->path[1], "/svc");
free(ns);
// /boot should be RX and /svc should be RW. We use kOpenFlagPosix + fuchsia.io.Node.NodeGetFlags
// to check that these are also the maximum rights supported.
fbl::unique_fd fd;
EXPECT_EQ(ZX_OK, fdio_open_fd("/boot",
fio::wire::kOpenRightReadable | fio::wire::kOpenRightExecutable |
fio::wire::kOpenFlagPosix,
fd.reset_and_get_address()));
EXPECT_EQ(fd_get_flags(std::move(fd)),
fio::wire::kOpenRightReadable | fio::wire::kOpenRightExecutable);
EXPECT_EQ(ZX_OK, fdio_open_fd("/svc",
fio::wire::kOpenRightReadable | fio::wire::kOpenRightWritable |
fio::wire::kOpenFlagPosix,
fd.reset_and_get_address()));
EXPECT_EQ(fd_get_flags(std::move(fd)),
fio::wire::kOpenRightReadable | fio::wire::kOpenRightWritable);
}
// We simply check here whether files can be opened with OPEN_RIGHT_EXECUTABLE or not.
//
// Also, this test really needs to be able to open specific files, which unfortunately means some
// specific lists of files to test against that make this a bit fragile. Opening subdirectories
// doesn't get us coverage because we only limit the rights that you can open the files with.
TEST(BootsvcIntegrationTest, BootfsExecutability) {
const char* kExecutableFiles[] = {
"/boot/pkg/bootsvc/bin/bootsvc", "/boot/pkg/dummy_pkg/lib/dummy.so",
"/boot/driver/fragment.so", "/boot/lib/dummy.so",
"/boot/kernel/vdso/full",
};
for (const char* file : kExecutableFiles) {
fbl::unique_fd fd;
EXPECT_EQ(ZX_OK,
fdio_open_fd(file, fio::wire::kOpenRightReadable | fio::wire::kOpenRightExecutable,
fd.reset_and_get_address()),
"open %s exec", file);
zx::vmo vmo;
EXPECT_EQ(ZX_OK, fdio_get_vmo_exec(fd.get(), vmo.reset_and_get_address()), "get_vmo_exec %s",
file);
EXPECT_TRUE(vmo.is_valid());
EXPECT_EQ(ZX_RIGHT_EXECUTE, get_rights(vmo) & ZX_RIGHT_EXECUTE);
}
const char* kNonExecutableFiles[] = {
"/boot/meta/fake.cm",
"/boot/kernel/counters/desc",
};
for (const char* file : kNonExecutableFiles) {
fbl::unique_fd fd;
EXPECT_EQ(ZX_ERR_ACCESS_DENIED,
fdio_open_fd(file, fio::wire::kOpenRightReadable | fio::wire::kOpenRightExecutable,
fd.reset_and_get_address()),
"open %s exec", file);
EXPECT_EQ(ZX_OK, fdio_open_fd(file, fio::wire::kOpenRightReadable, fd.reset_and_get_address()),
"open %s read-only", file);
zx::vmo vmo;
EXPECT_EQ(ZX_OK, fdio_get_vmo_clone(fd.get(), vmo.reset_and_get_address()), "get_vmo_clone %s",
file);
// Because of particulars of how fdio works with vmofiles, this returns INVALID_ARGS on failure
// instead of ACCESS_DENIED (basically because it fails during a handle_replace inside fdio).
EXPECT_EQ(ZX_ERR_INVALID_ARGS, fdio_get_vmo_exec(fd.get(), vmo.reset_and_get_address()),
"get_vmo_exec %s", file);
}
}
// Bootfs times should always be zero rather than following a system UTC thats isn't always
// available or reliable early in boot.
TEST(BootsvcIntegrationTest, BootfsFileTimes) {
const char* kTestPath = "/boot/pkg";
fbl::unique_fd fd;
struct stat s;
ASSERT_EQ(ZX_OK,
fdio_open_fd(kTestPath, fio::wire::kOpenRightReadable, fd.reset_and_get_address()),
"open %s for file time check", kTestPath);
ASSERT_EQ(0, fstat(fd.get(), &s), "get %s attributes", kTestPath);
EXPECT_EQ(0, s.st_ctim.tv_sec);
EXPECT_EQ(0, s.st_mtim.tv_sec);
}
// Make sure that bootsvc passed along program arguments from bootsvc.next
// correctly.
//
// As documented in TESTING, this test relies on these tests being run by using
// a boot cmdline that includes 'bootsvc.next=bin/bootsvc-integration-test,testargument' so
// that we can test the parsing on bootsvc.next.
TEST(BootsvcIntegrationTest, Arguments) {
ASSERT_EQ(arguments.size(), 2);
EXPECT_STR_EQ(arguments[0].c_str(), "bin/bootsvc-integration-test");
EXPECT_STR_EQ(arguments[1].c_str(), "testargument");
}
// Make sure the fuchsia.boot.FactoryItems service works
TEST(BootsvcIntegrationTest, FactoryItems) {
zx::channel local_items, remote_items;
zx_status_t status = zx::channel::create(0, &local_items, &remote_items);
ASSERT_EQ(ZX_OK, status);
// Check that we can open the fuchsia.boot.Items service.
status = fdio_service_connect(kItemsPath, remote_items.release());
ASSERT_EQ(ZX_OK, status);
zx::vmo payload;
uint32_t length;
// No factory items should appear here.
status = fuchsia_boot_ItemsGet(local_items.get(), ZBI_TYPE_STORAGE_BOOTFS_FACTORY, 0,
payload.reset_and_get_address(), &length);
ASSERT_EQ(ZX_OK, status);
ASSERT_FALSE(payload.is_valid());
ASSERT_EQ(0, length);
zx::channel local_factory_items, remote_factory_items;
status = zx::channel::create(0, &local_factory_items, &remote_factory_items);
ASSERT_EQ(ZX_OK, status);
// Check that we can open the fuchsia.boot.FactoryItems service.
status = fdio_service_connect(kFactoryItemsPath, remote_factory_items.release());
ASSERT_EQ(ZX_OK, status);
static constexpr char kExpected[] = "IAmAFactoryItemHooray";
uint8_t buf[sizeof(kExpected) - 1];
// Verify that multiple calls work.
for (int i = 0; i < 2; i++) {
status = fuchsia_boot_FactoryItemsGet(local_factory_items.get(), 0,
payload.reset_and_get_address(), &length);
ASSERT_EQ(ZX_OK, status);
ASSERT_TRUE(payload.is_valid());
ASSERT_EQ(sizeof(buf), length);
ASSERT_EQ(ZX_OK, payload.read(buf, 0, sizeof(buf)));
ASSERT_BYTES_EQ(reinterpret_cast<const uint8_t*>(kExpected), buf, sizeof(buf), "");
}
}
// Make sure the fuchsia.boot.Items service works
TEST(BootsvcIntegrationTest, BootItems) {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
ASSERT_EQ(ZX_OK, status);
// Check that we can open the fuchsia.boot.Items service.
status = fdio_service_connect(kItemsPath, remote.release());
ASSERT_EQ(ZX_OK, status);
// Check that we can get the following boot item types.
for (uint32_t type : (uint32_t[]){
ZBI_TYPE_CRASHLOG,
ZBI_TYPE_DEVICETREE,
ZBI_TYPE_DRV_BOARD_INFO,
ZBI_TYPE_PLATFORM_ID,
ZBI_TYPE_SERIAL_NUMBER,
ZBI_TYPE_STORAGE_RAMDISK,
}) {
zx::vmo payload;
uint32_t length;
status = fuchsia_boot_ItemsGet(local.get(), type, 0, payload.reset_and_get_address(), &length);
ASSERT_EQ(ZX_OK, status);
#ifdef __x64_64__
// (The following is only implemented on x64 at this time, so we only test
// it there.)
// If we see a ZBI_TYPE_CRASHLOG item, then the kernel should have
// translated it into a VMO file, and bootsvc should have put it at the
// path below.
if (type == ZBI_TYPE_CRASHLOG) {
ASSERT_TRUE(payload.is_valid());
fbl::String path = fbl::StringPrintf("/boot/%s", bootsvc::kLastPanicFilePath);
fbl::unique_fd fd(open(path.data(), O_RDONLY));
ASSERT_TRUE(fd.is_valid());
auto file_buf = std::make_unique<uint8_t[]>(length);
auto payload_buf = std::make_unique<uint8_t[]>(length);
ASSERT_EQ(length, read(fd.get(), file_buf.get(), length));
ASSERT_EQ(ZX_OK, payload.read(payload_buf.get(), 0, length));
ASSERT_BYTES_EQ(file_buf.get(), payload_buf.get(), length, "");
}
#endif
if (type == ZBI_TYPE_SERIAL_NUMBER) {
ASSERT_TRUE(payload.is_valid());
ASSERT_GE(length, 0);
std::string serial_no(length, '\0');
ASSERT_OK(payload.read(serial_no.data(), 0, length));
EXPECT_EQ(length, serial_no.size());
}
}
}
// Make sure fuchsia.boot.Items::GetBootloaderFile works
TEST(BootsvcIntegrationTest, BootloaderFiles) {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
// Check that we can open the fuchsia.boot.Items service.
ASSERT_OK(fdio_service_connect(kItemsPath, remote.release()));
constexpr char kInvalidBootloaderFileName[] = "This filename is too short";
zx_handle_t vmo_handle;
ASSERT_OK(fuchsia_boot_ItemsGetBootloaderFile(local.get(), kInvalidBootloaderFileName,
strlen(kInvalidBootloaderFileName), &vmo_handle));
ASSERT_EQ(ZX_HANDLE_INVALID, vmo_handle);
constexpr char kValidBootloaderFileName[] = "This is the filename of the file!";
constexpr char kValidBootloaderFilePayload[] = "FILE CONTENTS ARE HERE";
ASSERT_OK(fuchsia_boot_ItemsGetBootloaderFile(local.get(), kValidBootloaderFileName,
strlen(kValidBootloaderFileName), &vmo_handle));
ASSERT_NE(ZX_HANDLE_INVALID, vmo_handle);
zx::vmo vmo(vmo_handle);
ASSERT_TRUE(vmo.is_valid());
uint64_t size;
ASSERT_OK(vmo.get_property(ZX_PROP_VMO_CONTENT_SIZE, &size, sizeof(size)));
uint8_t buf[sizeof(kValidBootloaderFilePayload) - 1];
ASSERT_EQ(size, sizeof(buf));
ASSERT_OK(vmo.read(buf, 0, size));
ASSERT_BYTES_EQ(buf, kValidBootloaderFilePayload, sizeof(buf));
}
// Check that the kernel-provided VDSOs were added to /boot/kernel/vdso
TEST(BootsvcIntegrationTest, VdsosPresent) {
DIR* dir = opendir("/boot/kernel/vdso");
ASSERT_NOT_NULL(dir);
size_t count = 0;
dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (!strcmp(entry->d_name, ".")) {
continue;
}
ASSERT_EQ(entry->d_type, DT_REG);
++count;
}
ASSERT_GT(count, 0);
closedir(dir);
}
// Test that the boot arguments are extracted from both vbmeta and bootfs.
TEST(BootsvcIntegrationTest, BootArguments) {
EXPECT_STR_EQ(environ[0], "testkey=testvalue");
EXPECT_STR_EQ(environ[1], "bootfskey=bootfsvalue");
}
// Test that we can get the resources passed from the kernel.
TEST(BootsvcIntegrationTest, ResourcesAvailable) {
zx::resource mmio_resource(zx_take_startup_handle(PA_HND(PA_MMIO_RESOURCE, 0)));
ASSERT_TRUE(mmio_resource.is_valid());
zx::resource irq_resource(zx_take_startup_handle(PA_HND(PA_IRQ_RESOURCE, 0)));
ASSERT_TRUE(irq_resource.is_valid());
zx::resource system_resource(zx_take_startup_handle(PA_HND(PA_SYSTEM_RESOURCE, 0)));
ASSERT_TRUE(system_resource.is_valid());
#if __x86_64__
zx::resource ioport_resource(zx_take_startup_handle(PA_HND(PA_IOPORT_RESOURCE, 0)));
ASSERT_TRUE(ioport_resource.is_valid());
#elif __aarch64__
zx::resource smc_resource(zx_take_startup_handle(PA_HND(PA_SMC_RESOURCE, 0)));
ASSERT_TRUE(smc_resource.is_valid());
#endif
zx::resource root_resource(zx_take_startup_handle(PA_HND(PA_RESOURCE, 0)));
ASSERT_TRUE(root_resource.is_valid());
}
} // namespace
int main(int argc, char** argv) {
// Copy arguments for later use in tests.
for (int i = 0; i < argc; ++i) {
arguments.push_back(fbl::String(argv[i]));
}
int result = RUN_ALL_TESTS(argc, argv);
if (result == 0) {
print_test_success_string();
}
// Sleep 3 seconds to allow buffers to flush before powering off
zx::nanosleep(zx::deadline_after(zx::sec(3)));
// Return. WAIT, how does this test manage to finish if "success" is QEMU
// turning off? The ZBI that this is packaged on should set the
// bootsvc.on_next_process_exit flag so that bootsvc turns the system off
// when the "next process" (in this case, this test) exits.
return result;
}