blob: 7748036f6991101659b59d5f830427bd6352444d [file] [log] [blame]
// Copyright 2025 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 "custom-startup-test.h"
// This is a test component that's really just a test harness for running the
// static PIE implemented in static-pie-custom-startup-test.cc and feeding it
// the trivial custom bootstrap protocol described in the common header. The
// harness just delivers gtest failures if the static PIE crashes or fails to
// complete its side of the custom protocol before it exits.
//
// Though it starts the test process directly via zx::process::start to use the
// custom test protocol, this still uses the fuchsia.process.Launcher service.
// It doesn't need any special process creation privilege, just the component
// routing for that FIDL service.
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fidl/fuchsia.process/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/io.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/fit/function.h>
#include <lib/zx/job.h>
#include <lib/zx/result.h>
#include <lib/zx/socket.h>
#include <lib/zx/vmo.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <array>
#include <span>
#include <vector>
#include <fbl/unique_fd.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace {
namespace fio = fuchsia_io;
namespace fprocess = fuchsia_process;
class LibcCustomStartupTests : public ::testing::Test {
public:
void SetUp() override {
zx::result endpoints = fidl::CreateEndpoints<fprocess::Launcher>();
ASSERT_TRUE(endpoints.is_ok()) << endpoints.status_string();
zx_status_t status =
fdio_service_connect_by_name(fidl::DiscoverableProtocolName<fprocess::Launcher>,
endpoints->server.TakeChannel().release());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
launcher_.Bind(std::move(endpoints->client));
}
void TearDown() override {
if (job_) {
zx_status_t status = job_.kill();
EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status);
}
}
void GetJob(zx::job& job) {
ASSERT_FALSE(job_);
zx_status_t status = zx::job::create(*zx::job::default_job(), 0, &job_);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
status = job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &job);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
}
static void GetExecutable(const char* file, zx::vmo& vmo) {
constexpr fio::wire::Flags kFlags =
fio::wire::Flags::kProtocolFile | fio::wire::kPermReadable | fio::wire::kPermExecutable;
fbl::unique_fd fd;
zx_status_t status =
fdio_open3_fd(file, static_cast<uint64_t>(kFlags), fd.reset_and_get_address());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
status = fdio_get_vmo_exec(fd.get(), vmo.reset_and_get_address());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
}
auto& launcher() { return launcher_; }
static void Start(const fprocess::wire::ProcessStartData& data, zx::channel bootstrap) {
zx_status_t status = data.process.start(data.thread, data.entry, data.stack,
std::move(bootstrap), data.vdso_base);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
}
private:
fidl::WireSyncClient<fprocess::Launcher> launcher_;
zx::job job_;
};
TEST_F(LibcCustomStartupTests, CustomProtocol) {
fprocess::wire::LaunchInfo info = {.name = "custom-startup-test-child"};
ASSERT_NO_FATAL_FAILURE(GetJob(info.job));
ASSERT_NO_FATAL_FAILURE(
GetExecutable("/pkg/bin/static-pie-custom-startup-test", info.executable));
fidl::WireResult reply = launcher()->CreateWithoutStarting(std::move(info));
ASSERT_TRUE(reply.ok()) << reply.error();
ASSERT_EQ(reply.value().status, ZX_OK) << zx_status_get_string(reply.value().status);
// Create the channel for the custom protocol.
zx::channel bootstrap_parent, bootstrap_child;
zx_status_t status = zx::channel::create(0, &bootstrap_parent, &bootstrap_child);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
// Start the child running. It will wait for its bootstrap message.
ASSERT_NO_FATAL_FAILURE(Start(*reply.value().data, std::move(bootstrap_child)));
// Duplicate the process handle so it can be transferred.
zx::process process;
status = reply.value().data->process.duplicate(ZX_RIGHT_SAME_RIGHTS, &process);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
std::array<zx_handle_t, kMessageHandles> handles;
handles[kProcessSelfHandle] = reply.value().data->process.release();
handles[kThreadSelfHandle] = reply.value().data->thread.release();
handles[kAllocationVmarHandle] = reply.value().data->root_vmar.release();
// Decode the service's processargs message just enough for kImageVarHandle.
std::vector<std::byte> procargs_buffer;
std::vector<zx_handle_t> procargs_raw_handles;
procargs_buffer.resize(ZX_CHANNEL_MAX_MSG_BYTES);
procargs_raw_handles.resize(ZX_CHANNEL_MAX_MSG_HANDLES);
uint32_t actual_bytes, actual_handles;
status = reply.value().data->bootstrap.read(
0, procargs_buffer.data(), procargs_raw_handles.data(),
static_cast<uint32_t>(procargs_buffer.size()),
static_cast<uint32_t>(procargs_raw_handles.size()), &actual_bytes, &actual_handles);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
procargs_buffer.resize(actual_bytes);
procargs_raw_handles.resize(actual_handles);
std::vector<zx::handle> procargs_handles;
procargs_handles.reserve(actual_handles);
for (zx_handle_t h : procargs_raw_handles) {
procargs_handles.emplace_back(h);
}
zx_proc_args_t procargs_header;
ASSERT_GT(procargs_buffer.size(), sizeof(procargs_header));
memcpy(&procargs_header, procargs_buffer.data(), sizeof(procargs_header));
ASSERT_EQ(procargs_header.protocol, ZX_PROCARGS_PROTOCOL);
ASSERT_EQ(procargs_header.version, ZX_PROCARGS_VERSION);
ASSERT_LT(procargs_header.handle_info_off, procargs_buffer.size());
ASSERT_EQ(procargs_header.handle_info_off % sizeof(uint32_t), 0u);
ASSERT_GE(procargs_buffer.size() - procargs_header.handle_info_off,
procargs_handles.size() * sizeof(uint32_t));
std::span procargs_handle_info{
reinterpret_cast<const uint32_t*>(procargs_buffer.data() + procargs_header.handle_info_off),
procargs_handles.size()};
for (size_t i = 0; i < procargs_handles.size(); ++i) {
if (PA_HND_TYPE(procargs_handle_info[i]) == PA_VMAR_LOADED) {
handles[kImageVarHandle] = procargs_handles[i].release();
break;
}
}
// Finally, make a log socket to pass in.
zx::socket log_parent, log_child;
status = zx::socket::create(ZX_SOCKET_STREAM, &log_parent, &log_child);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
handles[kLogHandle] = log_child.release();
// The message in our trivial custom protocol is ready to send.
status = bootstrap_parent.write(0, kPing.data(), kPing.size(), handles.data(), handles.size());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
// Now wait for the process to reply and exit.
std::string log_text;
while (true) {
std::vector<zx_wait_item_t> wait_items;
std::vector<fit::function<void(zx_signals_t)>> on_item;
if (process) {
wait_items.push_back({
.handle = process.get(),
.waitfor = ZX_PROCESS_TERMINATED,
});
on_item.push_back([&process](zx_signals_t pending) {
if (pending & ZX_PROCESS_TERMINATED) {
zx_info_process_t info;
zx_status_t status =
process.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
ASSERT_TRUE(info.flags & ZX_INFO_PROCESS_FLAG_EXITED);
process.reset();
EXPECT_EQ(info.return_code, 0);
}
});
}
if (bootstrap_parent) {
wait_items.push_back({
.handle = bootstrap_parent.get(),
.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
});
on_item.push_back([&bootstrap_parent](zx_signals_t pending) {
if (pending & ZX_CHANNEL_READABLE) {
std::array<char, kPong.size()> buffer;
uint32_t actual;
zx_status_t status =
bootstrap_parent.read(0, buffer.data(), nullptr, buffer.size(), 0, &actual, nullptr);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
bootstrap_parent.reset();
EXPECT_EQ(actual, kPong.size());
EXPECT_EQ(std::string_view(buffer.data(), actual), kPong);
}
if (pending & ZX_CHANNEL_PEER_CLOSED) {
EXPECT_FALSE(bootstrap_parent); // Should have gotten pong already.
bootstrap_parent.reset();
}
});
}
if (log_parent) {
wait_items.push_back({
.handle = log_parent.get(),
.waitfor = ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED,
});
on_item.push_back([&log_parent, &log_text](zx_signals_t pending) {
if (pending & ZX_SOCKET_READABLE) {
std::string buffer;
buffer.resize(1024);
size_t actual;
zx_status_t status = log_parent.read(0, buffer.data(), buffer.size(), &actual);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
buffer.resize(actual);
log_text += buffer;
}
if (pending & ZX_SOCKET_PEER_CLOSED) {
log_parent.reset();
}
});
}
if (wait_items.empty()) {
break;
}
status = zx::handle::wait_many(wait_items.data(), static_cast<uint32_t>(wait_items.size()),
zx::time::infinite());
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
for (size_t i = 0; i < wait_items.size(); ++i) {
on_item[i](wait_items[i].pending);
}
}
EXPECT_TRUE(log_text.starts_with("{{{")) << log_text;
EXPECT_TRUE(log_text.ends_with(kLog)) << log_text;
}
} // anonymous namespace