blob: 23f4f26e1581778ac28601048db4e4b47849bcec [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "berberis/guest_loader/guest_loader.h"
#include <algorithm> // std::generate
#include <climits> // CHAR_BIT
#include <cstdint>
#include <cstdlib>
#include <functional> // std::ref
#include <mutex>
#include <random>
#include <thread>
#include "berberis/base/checks.h"
#include "berberis/base/config_globals.h" // SetMainExecutableRealPath
#include "berberis/base/stringprintf.h"
#include "berberis/base/tracing.h"
#include "berberis/guest_abi/guest_params.h"
#include "berberis/guest_os_primitives/guest_thread.h"
#include "berberis/guest_os_primitives/guest_thread_manager.h" // GetCurrentGuestThread
#include "berberis/guest_os_primitives/scoped_pending_signals.h"
#include "berberis/guest_state/guest_addr.h"
#include "berberis/guest_state/guest_state.h"
#include "berberis/kernel_api/sys_mman_emulation.h"
#include "berberis/proxy_loader/proxy_loader.h"
#include "berberis/runtime_primitives/host_call_frame.h"
#include "berberis/runtime_primitives/host_function_wrapper_impl.h" // MakeTrampolineCallable
#include "berberis/runtime_primitives/runtime_library.h" // ExecuteGuestCall
#include "berberis/tiny_loader/tiny_loader.h"
#include "native_bridge_support/linker/static_tls_config.h"
#include "private/CFIShadow.h" // kLibraryAlignment
#include "app_process.h"
#include "guest_loader_impl.h"
namespace berberis {
namespace {
const char* FindPtInterp(const LoadedElfFile* loaded_executable) {
const ElfPhdr* phdr_table = loaded_executable->phdr_table();
size_t phdr_count = loaded_executable->phdr_count();
ElfAddr load_bias = loaded_executable->load_bias();
for (size_t i = 0; i < phdr_count; ++i) {
const auto& phdr = phdr_table[i];
if (phdr.p_type == PT_INTERP) {
return reinterpret_cast<const char*>(load_bias + phdr.p_vaddr);
}
}
return nullptr;
}
void FillRandomBuf(uint8_t* buf, size_t size) {
// arc4random was introduced in GLIBC 2.36
#if defined(__GLIBC__) && ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 36)))
// Fall back to implementation-defined stl random
std::random_device random_device("/dev/urandom");
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t> engine(
random_device());
std::generate(buf, buf + size, std::ref(engine));
#else
// use arc4random for everything else
arc4random_buf(buf, size);
#endif
}
[[noreturn]] void StartGuestExecutableImpl(size_t argc,
const char* argv[],
char* envp[],
const LoadedElfFile* linker_elf_file,
const LoadedElfFile* main_executable_elf_file,
const LoadedElfFile* vdso_elf_file) {
GuestAddr main_executable_entry_point = ToGuestAddr(main_executable_elf_file->entry_point());
GuestAddr entry_point;
if (linker_elf_file->is_loaded()) {
entry_point = ToGuestAddr(linker_elf_file->entry_point());
} else {
// This is static executable. Entry point override only makes sense for static executables.
uintptr_t entry_point_override = GetEntryPointOverride();
if (entry_point_override != 0) {
entry_point = ToGuestAddr(reinterpret_cast<void*>(entry_point_override));
} else {
entry_point = main_executable_entry_point;
}
}
uint8_t kRandomBytes[16];
FillRandomBuf(kRandomBytes, sizeof(kRandomBytes));
GuestThread* main_thread = GetCurrentGuestThread();
ThreadState* state = main_thread->state();
ScopedPendingSignalsEnabler scoped_pending_signals_enabler(main_thread);
CPUState& cpu = state->cpu;
ScopedHostCallFrame host_call_frame(&cpu, entry_point);
GuestAddr updated_stack = InitKernelArgs(GetStackRegister(cpu),
argc,
argv,
envp,
ToGuestAddr(linker_elf_file->base_addr()),
main_executable_entry_point,
ToGuestAddr(main_executable_elf_file->phdr_table()),
main_executable_elf_file->phdr_count(),
ToGuestAddr(vdso_elf_file->base_addr()),
&kRandomBytes);
SetStackRegister(cpu, updated_stack);
// Main thread's stack contains envp and aux that may be used by other threads.
// Prevent stack unmap on main thread exit so the data remains available.
main_thread->DisallowStackUnmap();
ExecuteGuestCall(state);
FATAL("program '%s' didn't exit()", argv[0]);
}
// ATTENTION: Assume guest and host integer and pointer types match.
class FormatBufferGuestParamsArgs {
public:
// Capture ephemeral GuestVAListParams into internal params_ variable.
// GuestVAListParams is ephemeral in most cases because it's either produced from GuestParams or
// from std::va_list argument.
explicit FormatBufferGuestParamsArgs(GuestVAListParams&& params) : params_(params) {}
const char* GetCStr() { return params_.GetPointerParam<const char>(); }
uintmax_t GetPtrAsUInt() { return params_.GetParam<GuestAddr>(); }
intmax_t GetInt() { return params_.GetParam<int>(); }
intmax_t GetLong() { return params_.GetParam<long>(); }
intmax_t GetLongLong() { return params_.GetParam<long long>(); }
uintmax_t GetUInt() { return params_.GetParam<unsigned int>(); }
uintmax_t GetULong() { return params_.GetParam<unsigned long>(); }
uintmax_t GetULongLong() { return params_.GetParam<unsigned long long>(); }
intmax_t GetChar() { return params_.GetParam<int>(); }
uintmax_t GetSizeT() { return params_.GetParam<GuestAddr>(); }
private:
GuestVAListParams params_;
};
void TraceCallback(HostCode callee, ThreadState* state) {
UNUSED(callee);
if (Tracing::IsOn()) {
auto [format] = GuestParamsValues<void(const char*, ...)>(state);
FormatBufferGuestParamsArgs args{GuestParamsValues<void(const char*, ...)>(state)};
Tracing::TraceA(format, &args);
}
}
void PostInitCallback(HostCode callee, ThreadState* state) {
UNUSED(callee, state);
AppProcessPostInit();
}
void InterceptGuestSymbolCallback(HostCode callee, ThreadState* state) {
UNUSED(callee);
// Function prototype used here is the signature of native_bridge_intercept_symbol
auto [addr, lib_name, sym_name] =
GuestParamsValues<void(GuestAddr, const char*, const char* name)>(state);
InterceptGuestSymbol(addr, lib_name, sym_name, kProxyPrefix);
}
void ConfigStaticTlsCallback(HostCode callee, ThreadState* state) {
UNUSED(callee);
auto [config] = GuestParamsValues<void(const NativeBridgeStaticTlsConfig*)>(state);
state->thread->ConfigStaticTls(config);
}
void GetHostPthreadCallback(HostCode callee, ThreadState* state) {
UNUSED(callee);
auto&& [ret] = GuestReturnReference<decltype(pthread_self)>(state);
ret = pthread_self();
}
bool InitializeVdso(const LoadedElfFile& vdso_elf_file, std::string* error_msg) {
if (!MakeElfSymbolTrampolineCallable(
vdso_elf_file, "vdso", "native_bridge_trace", TraceCallback, nullptr, error_msg)) {
return false;
}
if (!MakeElfSymbolTrampolineCallable(vdso_elf_file,
"vdso",
"native_bridge_intercept_symbol",
InterceptGuestSymbolCallback,
nullptr,
error_msg)) {
return false;
}
if (!MakeElfSymbolTrampolineCallable(
vdso_elf_file, "vdso", "native_bridge_post_init", PostInitCallback, nullptr, error_msg)) {
return false;
}
void* call_guest_addr = vdso_elf_file.FindSymbol("native_bridge_call_guest");
if (call_guest_addr == nullptr) {
*error_msg = "couldn't find \"native_bridge_call_guest\" symbol in vdso";
return false;
}
InitHostCallFrameGuestPC(ToGuestAddr(call_guest_addr));
return true;
}
bool InitializeLinker(LinkerCallbacks* linker_callbacks,
const LoadedElfFile& linker_elf_file,
std::string* error_msg) {
if (!MakeElfSymbolTrampolineCallable(linker_elf_file,
"linker",
"__native_bridge_config_static_tls",
ConfigStaticTlsCallback,
nullptr,
error_msg)) {
return false;
}
if (!MakeElfSymbolTrampolineCallable(linker_elf_file,
"linker",
"__native_bridge_get_host_pthread",
GetHostPthreadCallback,
nullptr,
error_msg)) {
return false;
}
return InitializeLinkerCallbacks(linker_callbacks, linker_elf_file, error_msg) &&
InitializeLinkerCallbacksArch(linker_callbacks, linker_elf_file, error_msg);
}
std::mutex g_guest_loader_instance_mtx;
GuestLoader* g_guest_loader_instance;
} // namespace
GuestLoader::GuestLoader() = default;
GuestLoader::~GuestLoader() = default;
GuestLoader* GuestLoader::CreateInstance(const char* main_executable_path,
const char* vdso_path,
const char* loader_path,
std::string* error_msg) {
std::lock_guard<std::mutex> lock(g_guest_loader_instance_mtx);
CHECK(g_guest_loader_instance == nullptr);
TRACE(
"GuestLoader::CreateInstance(main_executable_path=\"%s\", "
"vdso_path=\"%s\", loader_path=\"%s\")",
main_executable_path,
vdso_path,
loader_path);
std::unique_ptr<GuestLoader> instance(new GuestLoader());
if (!TinyLoader::LoadFromFile(main_executable_path,
kLibraryAlignment,
&MmapForGuest,
&MunmapForGuest,
&instance->executable_elf_file_,
error_msg)) {
return nullptr;
}
// For readlink(/proc/self/exe).
SetMainExecutableRealPath(main_executable_path);
instance->main_executable_path_ = main_executable_path;
// Initialize caller_addr_ to executable entry point.
instance->caller_addr_ = instance->executable_elf_file_.entry_point();
// Real pt_interp is only used to distinguish static executables.
bool is_static_executable = (FindPtInterp(&instance->executable_elf_file_) == nullptr);
if (TinyLoader::LoadFromFile(vdso_path,
kLibraryAlignment,
&MmapForGuest,
&MunmapForGuest,
&instance->vdso_elf_file_,
error_msg)) {
if (!InitializeVdso(instance->vdso_elf_file_, error_msg)) {
return nullptr;
}
} else {
if (!is_static_executable) {
return nullptr;
}
}
if (is_static_executable) {
InitializeLinkerCallbacksToStubs(&instance->linker_callbacks_);
if (instance->executable_elf_file_.e_type() == ET_DYN) {
// Special case - ET_DYN executable without PT_INTERP, consider linker.
TRACE("pretend running linker as main executable");
if (!InitializeLinker(
&instance->linker_callbacks_, instance->executable_elf_file_, error_msg)) {
// Not the right linker, warn and hope for the best.
TRACE("failed to init main executable as linker, running as is");
}
}
} else {
if (!TinyLoader::LoadFromFile(loader_path,
kLibraryAlignment,
&MmapForGuest,
&MunmapForGuest,
&instance->linker_elf_file_,
error_msg)) {
return nullptr;
}
if (!InitializeLinker(&instance->linker_callbacks_, instance->linker_elf_file_, error_msg)) {
return nullptr;
}
InitLinkerDebug(instance->linker_elf_file_);
}
g_guest_loader_instance = instance.release();
return g_guest_loader_instance;
}
GuestLoader* GuestLoader::GetInstance() {
std::lock_guard<std::mutex> lock(g_guest_loader_instance_mtx);
CHECK(g_guest_loader_instance != nullptr);
return g_guest_loader_instance;
}
void GuestLoader::StartGuestMainThread() {
std::thread t(StartGuestExecutableImpl,
1,
&main_executable_path_,
environ,
&linker_elf_file_,
&executable_elf_file_,
&vdso_elf_file_);
t.detach();
WaitForAppProcess();
}
void GuestLoader::StartGuestExecutable(size_t argc, const char* argv[], char* envp[]) {
StartGuestExecutableImpl(
argc, argv, envp, &linker_elf_file_, &executable_elf_file_, &vdso_elf_file_);
}
GuestLoader* GuestLoader::StartAppProcessInNewThread(std::string* error_msg) {
GuestLoader* instance = CreateInstance(kAppProcessPath, kVdsoPath, kPtInterpPath, error_msg);
if (instance) {
instance->StartGuestMainThread();
}
return instance;
}
void GuestLoader::StartExecutable(const char* main_executable_path,
const char* vdso_path,
const char* loader_path,
size_t argc,
const char* argv[],
char* envp[],
std::string* error_msg) {
GuestLoader* instance = CreateInstance(main_executable_path,
vdso_path ? vdso_path : kVdsoPath,
loader_path ? loader_path : kPtInterpPath,
error_msg);
if (instance) {
instance->StartGuestExecutable(argc, argv, envp);
}
}
const struct r_debug* GuestLoader::FindRDebug() const {
if (executable_elf_file_.is_loaded() && executable_elf_file_.dynamic() != nullptr) {
for (const ElfDyn* d = executable_elf_file_.dynamic(); d->d_tag != DT_NULL; ++d) {
if (d->d_tag == DT_DEBUG) {
return reinterpret_cast<const struct r_debug*>(d->d_un.d_val);
}
}
}
return nullptr;
}
bool MakeElfSymbolTrampolineCallable(const LoadedElfFile& elf_file,
const char* elf_file_label,
const char* symbol_name,
void (*callback)(HostCode, ThreadState*),
HostCode arg,
std::string* error_msg) {
void* symbol_addr = elf_file.FindSymbol(symbol_name);
if (symbol_addr == nullptr) {
*error_msg = StringPrintf("couldn't find \"%s\" symbol in %s", symbol_name, elf_file_label);
return false;
}
MakeTrampolineCallable(ToGuestAddr(symbol_addr), false, callback, arg, symbol_name);
return true;
}
} // namespace berberis