blob: 556c5ad292cd0457694b08000529b385ddbc5f84 [file] [log] [blame]
// Copyright 2024 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 "src/devices/bin/driver_loader/loader.h"
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/io.h>
#include <lib/fit/defer.h>
#include <lib/ld/abi.h>
#include <lib/ld/remote-abi-heap.h>
#include <lib/ld/remote-abi-stub.h>
#include <lib/ld/remote-abi.h>
#include <filesystem>
#include <fbl/unique_fd.h>
#include "src/devices/bin/driver_loader/diagnostics.h"
#include "src/devices/lib/log/log.h"
namespace fio = fuchsia_io;
namespace {
zx::result<zx::vmo> GetStubLdVmo() {
std::filesystem::path stub_path{"/pkg/lib"};
stub_path /= std::string{ld::RemoteAbiStub<>::kFilename};
const 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(stub_path.c_str(), static_cast<uint64_t>(kFlags), fd.reset_and_get_address());
if (status != ZX_OK) {
return zx::error(status);
}
zx::vmo vmo;
status = fdio_get_vmo_exec(fd.get(), vmo.reset_and_get_address());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(vmo));
}
} // namespace
namespace driver_loader {
// static
std::unique_ptr<Loader> Loader::Create(async_dispatcher_t* dispatcher,
LoadDriverHandler load_driver_handler_for_testing) {
zx::result<zx::vmo> stub_ld_vmo = GetStubLdVmo();
if (stub_ld_vmo.is_error()) {
LOGF(ERROR, "Failed to get stub ld vmo for loader: %s", stub_ld_vmo.status_string());
return nullptr;
}
auto diag = MakeDiagnostics();
return std::unique_ptr<Loader>(new Loader(dispatcher, std::move(load_driver_handler_for_testing),
diag, std::move(*stub_ld_vmo)));
}
void Loader::Connect(fidl::ServerEnd<fuchsia_driver_loader::DriverHostLauncher> server_end) {
bindings_.AddBinding(dispatcher_, std::move(server_end), this, fidl::kIgnoreBindingClosure);
}
zx::result<> Loader::Start(zx::process process, zx::vmar root_vmar, zx::vmo exec_vmo,
zx::vmo vdso_vmo, fidl::ClientEnd<fuchsia_io::Directory> lib_dir,
fidl::ServerEnd<fuchsia_driver_loader::DriverHost> server_end) {
auto diag = MakeDiagnostics();
Linker linker;
linker.set_abi_stub(remote_abi_stub_);
if (!linker.abi_stub()) {
return zx::error(ZX_ERR_INTERNAL);
}
Linker::Module::DecodedPtr decoded_executable =
Linker::Module::Decoded::Create(diag, std::move(exec_vmo), kPageSize);
if (!decoded_executable) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// TODO(https://fxbug.dev/365122208): we should cache the decoded ptr per vdso version.
Linker::Module::DecodedPtr decoded_vdso =
Linker::Module::Decoded::Create(diag, std::move(vdso_vmo), kPageSize);
if (!decoded_vdso) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
auto init_result = linker.Init(diag,
{Linker::Executable(std::move(decoded_executable)),
Linker::Implicit(std::move(decoded_vdso))},
GetDepFunction(diag, std::move(lib_dir)));
if (!init_result) {
// We usually do not expect this to fail. It's possible that there are errors
// reported to the diagnostics (such as for missing deps), but |init_result| will still be
// true. For non fatal errors, we will try to proceed as far as possible in order to catch as
// many issues as possible.
LOGF(ERROR, "Linker init failed, probably due to a very invalid module");
return zx::error(ZX_ERR_INTERNAL);
}
// We expect this to be equal to the size of the initial_modules list passed to |linker.Init|.
ZX_ASSERT_MSG(init_result->size() == 2u,
"Linker.Init returned an unexpected number of elements, want %u got %lu", 2u,
init_result->size());
// Allocate new child vmars.
if (!linker.Allocate(diag, root_vmar.borrow())) {
// As |Init| has succeeded, |Allocate| should only fail due to system errors such as resource
// constraints, rather than issues with the module itself. This may be due to kernel OOM, or
// that there are too many existing mappings.
LOGF(ERROR, "Linker failed to allocate in vmar, address space may be full");
return zx::error(ZX_ERR_INTERNAL);
}
const Linker::Module& loaded_vdso = *init_result->back();
struct ProcessState::ProcessStartArgs process_start_args = {
.entry = linker.main_entry(),
.vdso_base = loaded_vdso.module().vaddr_start(),
.stack_size = linker.main_stack_size(),
};
// Apply relocations to segment VMOs.
if (!linker.Relocate(diag)) {
return zx::error(ZX_ERR_INTERNAL);
}
// Since |DiagnosticsFlags::multiple_errors| is set to true, we would not have returned early
// for non-fatal errors or warnings. Check now before attempting to continue loading.
auto check_errors = [&diag](std::string_view what, std::string_view error_msg) -> zx::result<> {
if ((diag.errors() != 0) || (diag.warnings() != 0)) {
LOGF(ERROR, "Linker diagnostics reported %u errors, %u warnings during %s, %s aborted. %s.",
diag.errors(), diag.warnings(), what, what, error_msg);
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok();
};
if (auto result = check_errors("relocation", "This is likely due to a bad driver host package");
result.is_error()) {
return result.take_error();
}
// Load the segments into the allocated vmars.
if (!linker.Load(diag)) {
return zx::error(ZX_ERR_INTERNAL);
}
if (auto result = check_errors("loading", "This is an unexpected error"); result.is_error()) {
return result.take_error();
}
// Commit the VMARs and mappings.
linker.Commit();
constexpr std::string_view kThreadName = "driver-host-initial-thread";
zx::thread thread;
zx_status_t status =
zx::thread::create(process, kThreadName.data(), kThreadName.size(), 0, &thread);
if (status != ZX_OK) {
return zx::error(status);
}
zx::channel bootstrap_sender, bootstrap_receiver;
status = zx::channel::create(0, &bootstrap_sender, &bootstrap_receiver);
if (status != ZX_OK) {
return zx::error(status);
}
auto preloaded_modules = linker.PreloadedImplicit(*init_result);
// Start the process.
auto process_state = ProcessState::Create(
remote_abi_stub_, std::move(process), std::move(thread), std::move(root_vmar),
std::move(bootstrap_sender), load_driver_handler_for_testing_.share(),
std::move(process_start_args), std::move(preloaded_modules));
if (process_state.is_error()) {
LOGF(ERROR, "Failed to create process state: %s", process_state.status_string());
return process_state.take_error();
}
status = process_state->BindServer(dispatcher_, std::move(server_end));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to bind server endpoint to process state: %s",
zx_status_get_string(status));
return zx::error(status);
}
status = process_state->Start(std::move(bootstrap_receiver));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to start process: %s", zx_status_get_string(status));
return zx::error(status);
}
started_processes_.push_back(std::move(*process_state));
return zx::ok();
}
void Loader::Launch(LaunchRequestView request, LaunchCompleter::Sync& completer) {
auto result = Start(std::move(request->process()), std::move(request->root_vmar()),
std::move(request->driver_host_binary()), std::move(request->vdso()),
std::move(request->driver_host_libs()), std::move(request->driver_host()));
if (result.is_error()) {
completer.ReplyError(result.status_value());
return;
}
completer.ReplySuccess();
}
// static
zx::result<zx::vmo> Loader::GetDepVmo(fidl::UnownedClientEnd<fuchsia_io::Directory> lib_dir,
const char* libname) {
auto [client_end, server_end] = fidl::Endpoints<fio::File>::Create();
zx_status_t status =
fdio_open3_at(lib_dir.channel()->get(), libname,
static_cast<uint64_t>(fio::wire::kPermReadable | fio::wire::kPermExecutable),
server_end.TakeChannel().release());
if (status != ZX_OK) {
return zx::error(status);
}
fidl::SyncClient file(std::move(client_end));
auto result = file->GetBackingMemory(fio::VmoFlags::kRead | fio::VmoFlags::kExecute |
fio::VmoFlags::kPrivateClone);
if (result.is_error()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
return zx::ok(std::move(result->vmo()));
}
// static
zx::result<std::unique_ptr<Loader::ProcessState>> Loader::ProcessState::Create(
ld::RemoteAbiStub<>::Ptr remote_abi_stub, zx::process process, zx::thread thread,
zx::vmar root_vmar, zx::channel bootstrap_sender,
LoadDriverHandler load_driver_handler_for_testing, const ProcessStartArgs& args,
Linker::InitModuleList preloaded_modules) {
auto process_state = std::unique_ptr<ProcessState>(new ProcessState());
process_state->remote_abi_stub_ = std::move(remote_abi_stub);
process_state->process_ = std::move(process);
process_state->thread_ = std::move(thread);
process_state->root_vmar_ = std::move(root_vmar);
process_state->process_start_args_ = std::move(args);
process_state->preloaded_modules_ = std::move(preloaded_modules);
process_state->bootstrap_sender_ = std::move(bootstrap_sender);
process_state->load_driver_handler_for_testing_ = std::move(load_driver_handler_for_testing);
zx_status_t status = process_state->AllocateStack();
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(process_state));
}
// TODO(https://fxbug.dev/349913885): this should be replaced by a call to a helper library
// once available.
zx_status_t Loader::ProcessState::AllocateStack() {
zx::vmo stack_vmo;
size_t bootstrap_stack_size = process_start_args_.stack_size.value_or(kDefaultStackSize);
const size_t guard_size = Loader::kPageSize;
const size_t stack_vmo_size = (bootstrap_stack_size + Loader::kPageSize - 1) & -Loader::kPageSize;
const size_t stack_vmar_size = stack_vmo_size + guard_size;
zx_status_t status = zx::vmo::create(stack_vmo_size, 0, &stack_vmo);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to create vmo for stack: %s", zx_status_get_string(status));
return status;
}
zx::vmar stack_vmar;
uintptr_t stack_vmar_base;
status = root_vmar_.allocate(ZX_VM_CAN_MAP_SPECIFIC | ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE, 0,
stack_vmar_size, &stack_vmar, &stack_vmar_base);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to allocate child vmar for stack: %s", zx_status_get_string(status));
return status;
}
zx_vaddr_t stack_base;
status = stack_vmar.map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_SPECIFIC | ZX_VM_ALLOW_FAULTS,
guard_size, stack_vmo, 0, stack_vmo_size, &stack_base);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to map stack vmar: %s", zx_status_get_string(status));
return status;
}
stack_ = std::move(stack_vmo);
initial_stack_pointer_ = elfldltl::AbiTraits<>::InitialStackPointer(stack_base, stack_vmo_size);
return ZX_OK;
}
zx_status_t Loader::ProcessState::BindServer(
async_dispatcher_t* dispatcher, fidl::ServerEnd<fuchsia_driver_loader::DriverHost> server_end) {
if (binding_.has_value()) {
return ZX_ERR_ALREADY_BOUND;
}
binding_.emplace(dispatcher, std::move(server_end), this, fidl::kIgnoreBindingClosure);
return ZX_OK;
}
zx_status_t Loader::ProcessState::Start(zx::channel bootstrap_receiver) {
return process_.start(thread_, process_start_args_.entry, initial_stack_pointer_,
std::move(bootstrap_receiver), process_start_args_.vdso_base);
}
void Loader::ProcessState::LoadDriver(LoadDriverRequestView request,
LoadDriverCompleter::Sync& completer) {
fidl::VectorView<fuchsia_driver_loader::wire::RootModule> additional_root_modules;
if (request->has_additional_root_modules()) {
additional_root_modules = request->additional_root_modules();
}
auto result = LoadDriverModule(std::string(request->driver_soname().get()),
std::move(request->driver_binary()),
std::move(request->driver_libs()), additional_root_modules);
if (result.is_error()) {
completer.ReplyError(result.status_value());
return;
}
fidl::Arena arena;
completer.ReplySuccess(fuchsia_driver_loader::wire::DriverHostLoadDriverResponse::Builder(arena)
.dynamic_linking_abi(*result)
.Build());
}
zx::result<Loader::DynamicLinkingPassiveAbi> Loader::ProcessState::LoadDriverModule(
std::string driver_name, zx::vmo driver_module, fidl::ClientEnd<fuchsia_io::Directory> lib_dir,
fidl::VectorView<fuchsia_driver_loader::wire::RootModule> additional_root_modules) {
auto diag = MakeDiagnostics();
if (driver_name == "compat.so") {
driver_name = "libdriver.so";
}
Linker::Soname root_module_name{driver_name};
Linker linker;
linker.set_abi_stub(remote_abi_stub_);
Linker::Module::DecodedPtr decoded_module =
Linker::Module::Decoded::Create(diag, std::move(driver_module), kPageSize);
if (!decoded_module) {
LOGF(ERROR, "Failed to decode driver module for %s", root_module_name.c_str());
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Make a copy of the list of driver host initial modules that will be in the
// driver's dynamic linking namespace - this is the driver host (libdriver.so) and the vDSO.
Linker::InitModuleList initial_modules = preloaded_modules_;
initial_modules.emplace_back(Linker::RootModule(decoded_module, root_module_name));
// We need to keep these strings alive for |Linker.Init|.
std::vector<std::string> additional_root_modules_names;
for (auto& module : additional_root_modules) {
additional_root_modules_names.emplace_back(module.name().get());
Linker::Soname name{additional_root_modules_names.back()};
Linker::Module::DecodedPtr decoded_module =
Linker::Module::Decoded::Create(diag, std::move(module.binary()), kPageSize);
if (!decoded_module) {
LOGF(ERROR, "Failed to decode additional root module module %s", name.c_str());
return zx::error(ZX_ERR_INVALID_ARGS);
}
initial_modules.emplace_back(Linker::RootModule(decoded_module, name));
}
auto init_result =
linker.Init(diag, std::move(initial_modules), GetDepFunction(diag, std::move(lib_dir)));
if (!init_result) {
// We usually do not expect this to fail. It's possible that there are errors
// reported to the diagnostics (such as for missing deps), but |init_result| will still be
// true. For non fatal errors, we will try to proceed as far as possible in order to catch as
// many issues as possible.
LOGF(ERROR, "Linker init failed, probably due to a very invalid module");
return zx::error(ZX_ERR_INVALID_ARGS);
}
// We expect the |init_result| vector to be the same size as the number of |initial_modules|.
// This includes the driver module, the two preloaded modules (driver host and vdso),
// and any additional root modules.
size_t expected_init_result_size = 3u + additional_root_modules.size();
ZX_ASSERT_MSG(init_result->size() == expected_init_result_size,
"Linker.Init returned an unexpected number of elements, want %lu got %lu",
expected_init_result_size, init_result->size());
if (!linker.Allocate(diag, root_vmar_.borrow())) {
// As |Init| has succeeded, |Allocate| should only fail due to system errors such as resource
// constraints, rather than issues with the module itself. This may be due to kernel OOM, or
// that there are too many existing mappings.
LOGF(ERROR, "Linker failed to allocate in vmar, address space may be full");
return zx::error(ZX_ERR_INTERNAL);
}
if (!linker.Relocate(diag)) {
return zx::error(ZX_ERR_INTERNAL);
}
// Since |DiagnosticsFlags::multiple_errors| is set to true, we would not have returned early
// for non-fatal errors or warnings. Check now before attempting to continue loading.
auto check_errors = [&diag](std::string_view what, std::string_view error_msg) -> zx::result<> {
if ((diag.errors() != 0) || (diag.warnings() != 0)) {
LOGF(ERROR, "Linker diagnostics reported %u errors, %u warnings during %s, %s aborted. %s.",
diag.errors(), diag.warnings(), what, what, error_msg);
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok();
};
if (auto result = check_errors("relocation", "This is likely due to a bad driver host package");
result.is_error()) {
return result.take_error();
}
if (!linker.Load(diag)) {
return zx::error(ZX_ERR_INTERNAL);
}
if (auto result = check_errors("loading", "This is an unexpected error"); result.is_error()) {
return result.take_error();
}
linker.Commit();
DynamicLinkingPassiveAbi passive_abi = linker.abi_vaddr();
ZX_ASSERT_MSG(passive_abi != 0u, "Got null for dynamic linking passive abi address");
if (load_driver_handler_for_testing_) {
load_driver_handler_for_testing_(bootstrap_sender_.borrow(), passive_abi);
}
return zx::ok(passive_abi);
}
} // namespace driver_loader