blob: a82e911c9447e2e775a044ef46c80e6e5621954b [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 <lib/elfldltl/diagnostics.h>
#include <lib/elfldltl/machine.h>
#include <lib/elfldltl/phdr.h>
#include <lib/elfldltl/relro.h>
#include <lib/ld/abi.h>
#include <lib/ld/bootstrap.h>
#include <lib/ld/module.h>
#include <lib/ld/tls.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <atomic>
#include <cassert>
#include "startup-relocate.h"
// This file is only linked into a static PIE that gets __libc_start_main from
// libc.a; in libc.so stub-relocate.cc defines StartupRelocate no-op methods.
//
// When linked in, this file also provides the _ld_abi linkage symbol as the
// startup dynamic linker (or remote stub dynamic linker) normally would. Code
// in libc (or elsewhere) that uses _ld_abi to find the ELF modules can work as
// it would with dynamic linking, but it will see just the executable and vDSO.
namespace LIBC_NAMESPACE_DECL {
namespace {
// As the startup dynamic linker does, _ld_abi and the data it points to are
// all made read-only before application code runs. Here that's done by
// placing the variables explicitly in the executable's RELRO segment at link
// time. The ProtectRelro method will make all those pages read-only not long
// after StartupRelocate is done initializing the data.
#define RELRO(type, name, ...) \
[[gnu::section(".data.rel.ro." #name)]] constinit type name __VA_ARGS__
using Abi = ld::abi::Abi<>;
using Elf = elfldltl::Elf<>;
using Self = elfldltl::Self<>;
using Tls = elfldltl::TlsTraits<>;
using RelroObserver = elfldltl::PhdrRelroObserver<Elf>;
using StackObserver = elfldltl::PhdrStackObserver<Elf>;
using TlsObserver = elfldltl::PhdrTlsObserver<Elf>;
using PreinitObserver = elfldltl::DynamicPreinitObserver<Elf>;
using InitObserver = elfldltl::DynamicInitObserver<Elf>;
using FiniObserver = elfldltl::DynamicFiniObserver<Elf>;
extern Abi::Module gSelfModule, gVdsoModule;
// First in the module list: the executable.
RELRO(Abi::Module, gSelfModule) = {
.link_map = {.next{&gVdsoModule.link_map}},
.symbols{elfldltl::kLinkerZeroInitialized},
.symbolizer_modid = 0,
.symbols_visible = true,
};
RELRO(Abi::Module, gVdsoModule) = {
.link_map = {.prev{&gSelfModule.link_map}},
.symbols{elfldltl::kLinkerZeroInitialized},
.symbolizer_modid = 1,
.symbols_visible = true,
};
using SingleTlsModule = std::array<Abi::TlsModule, 1>;
using SingleTlsOffset = std::array<Elf::Addr, 1>;
RELRO(SingleTlsModule, gTlsModules);
RELRO(SingleTlsOffset, gTlsOffsets);
} // namespace
// TLS fields are initialized in StartupRelocate if there is a PT_TLS segment.
extern "C" RELRO(Abi, mutable_ld_abi, __asm__("_ld_abi"));
constinit decltype(mutable_ld_abi) mutable_ld_abi = {
.loaded_modules{&gSelfModule},
.loaded_modules_count{2},
};
StartupRelocate::StartupRelocate(const void* vdso_base) {
// Do the bootstrap relocation so system calls can be made.
auto bootstrap_diag = elfldltl::TrapDiagnostics();
std::optional<Elf::Phdr> relro_phdr, tls_phdr;
std::optional<Elf::size_type> stack_size;
std::span<const Elf::Addr> preinit_array;
ld::Bootstrap bootstrap{
bootstrap_diag,
vdso_base,
[]() { return zx_system_get_page_size(); }, // Must be after relocation.
gSelfModule,
gVdsoModule,
// Collect information for _ld_abi along the way.
std::forward_as_tuple( // Phdr observers.
RelroObserver{relro_phdr}, StackObserver{stack_size}, TlsObserver{tls_phdr}),
std::forward_as_tuple( // Dynamic observers.
InitObserver{gSelfModule.init}, FiniObserver{gSelfModule.init},
PreinitObserver{preinit_array}),
};
mutable_ld_abi.preinit_array = preinit_array;
// After the bootstrap protocol acquires the VMAR handle, the RELRO data can
// be protected. First, this function modifies some of that RELRO data.
if (relro_phdr) [[likely]] {
std::tie(start_, size_) = // ProtectRelro will use these.
elfldltl::RelroBounds(*relro_phdr, bootstrap.page_size());
start_ += gSelfModule.link_map.addr; // Apply the load bias.
}
if (!stack_size) [[unlikely]] {
ZX_PANIC("PT_GNU_STACK must specify a p_memsz; use -Wl,-z,stack-size=...");
}
mutable_ld_abi.stack_size = *stack_size;
// If the executable has a PT_TLS, fill out all the TLS bits in _ld_abi.
if (tls_phdr) {
const Elf::Phdr& tls = *tls_phdr;
auto memory = Self::Memory();
std::span tdata = *memory.ReadArray<std::byte>(tls.vaddr, tls.filesz);
gSelfModule.tls_modid = 1;
auto& tls_module = gTlsModules.front();
auto& tls_offset = gTlsOffsets.front();
tls_module = {.tls_initial_data = tdata,
.tls_bss_size = tls.memsz - tls.filesz,
.tls_alignment = tls.align};
tls_offset = mutable_ld_abi.static_tls_layout.Assign(tls.memsz, tls.align);
mutable_ld_abi.static_tls_modules = std::span(gTlsModules);
mutable_ld_abi.static_tls_offsets = std::span(gTlsOffsets);
}
// At this point everything looks pretty much like it would have looked at
// the program entry point after dynamic linking. The main difference left
// is that the startup dynamic linker would have write-protected the pages
// containing _ld_abi and what it points to. That can't be done until the
// bootstrap protocol acquires the VMAR handle and calls ProtectRelro,
// below. All the stores to RELRO data must be ordered before that.
std::atomic_signal_fence(std::memory_order_release);
}
// The (only) VMAR handle will be closed on return, so the region can never be
// made writable again later.
void StartupRelocate::ProtectRelro(zx::vmar loaded_vmar) const&& {
assert(loaded_vmar);
zx_status_t status = loaded_vmar.protect(ZX_VM_PERM_READ, start_, size_);
ZX_ASSERT_MSG(status == ZX_OK, "cannot protect RELRO: %s", zx_status_get_string(status));
}
} // namespace LIBC_NAMESPACE_DECL