blob: f232e3025777da29a225b87180e4a0db783866cc [file] [log] [blame]
// Copyright 2019 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.
// This program is used to test the process_builder library's handling of
// statically linked PIE executables.
//
// It implements just enough ELF parsing to look up syscall symbols from the
// vDSO using the GNU hash table, get the zx_channel_read/write symbols, read
// the processargs bootstrap message to find another channel handle with type
// PA_USER0, and then reads a message from that channel and echos it back on the
// same channel. The test uses this echo to confirm that the process was loaded
// correctly.
#include <elf.h>
#include <stddef.h>
#include <stdint.h>
#include <zircon/processargs.h>
#include <zircon/types.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
typedef void (*zx_process_exit_t)(int64_t /*retcode*/);
typedef zx_status_t (*zx_channel_write_t)(zx_handle_t /*handle*/,
uint32_t /*options*/,
const void* /*bytes*/,
uint32_t /*num_bytes*/,
const zx_handle_t* /*handles*/,
uint32_t /*num_handles*/);
typedef zx_status_t (*zx_channel_read_t)(
zx_handle_t /*handle*/, uint32_t /*options*/, void* /*bytes*/,
zx_handle_t* /*handles*/, uint32_t /*num_bytes*/, uint32_t /*num_handles*/,
uint32_t* /*actual_bytes*/, uint32_t* /*actual_handles*/);
// Get a memory load address from a base load address and unadjusted virtual
// address.
static void* laddr(const void* base, size_t vaddr) {
return (uint8_t*)base + vaddr;
}
static const Elf64_Dyn* search_dyn(const Elf64_Dyn* dyn_array,
Elf64_Sxword tag) {
for (; dyn_array->d_tag != tag; ++dyn_array) {
if (dyn_array->d_tag == DT_NULL) {
return NULL;
}
}
return dyn_array;
}
typedef struct {
uint32_t nbuckets;
uint32_t symoffset;
uint32_t bloom_size;
uint32_t bloom_shift;
uint64_t* bloom; // uint64_t[bloom_size]
uint32_t* buckets; // uint32_t[nbuckets]
uint32_t* chain;
} GnuHashTable;
static GnuHashTable read_gnu_hash_table(const uint32_t* addr) {
uint32_t nbuckets = addr[0];
uint32_t bloom_size = addr[2];
uint64_t* bloom = (uint64_t*)&addr[4];
uint32_t* buckets = (uint32_t*)&bloom[bloom_size];
uint32_t* chain = &buckets[nbuckets];
GnuHashTable ret = {
.nbuckets = nbuckets,
.symoffset = addr[1],
.bloom_size = bloom_size,
.bloom_shift = addr[3],
.bloom = bloom,
.buckets = buckets,
.chain = chain,
};
return ret;
}
static int strcmp(const char* l, const char* r) {
while (*l == *r && *l) {
++l, ++r;
}
return *(unsigned char*)l - *(unsigned char*)r;
}
static uint32_t gnu_hash(const char* name) {
const uint8_t* s = (uint8_t*)name;
uint32_t hash = 5381;
for (; *s; s++) {
hash += hash * 32 + *s;
}
return hash;
}
static const Elf64_Sym* lookup_sym(const char* name,
const GnuHashTable* hashtab,
const Elf64_Sym* symtab,
const char* strtab) {
// Not bothering with bloom filter, don't need best possible lookup speed.
uint32_t lookup_hash = gnu_hash(name);
uint32_t bucket = lookup_hash % hashtab->nbuckets;
uint32_t chain_start = hashtab->buckets[bucket];
if (chain_start < hashtab->symoffset) {
return NULL;
}
for (size_t i = chain_start;; ++i) {
uint32_t chain_hash = hashtab->chain[i - hashtab->symoffset];
const char* symname = strtab + symtab[i].st_name;
if ((chain_hash | 1) == (lookup_hash | 1) && strcmp(name, symname) == 0) {
return &symtab[i];
}
if (chain_hash & 1) {
// Reached end of chain, lookup failed.
break;
}
}
return NULL;
}
static const void* lookup_func(const char* name, const GnuHashTable* hashtab,
const Elf64_Sym* symtab, const char* strtab,
const void* base) {
const Elf64_Sym* sym = lookup_sym(name, hashtab, symtab, strtab);
if (!sym || ELF64_ST_TYPE(sym->st_info) != STT_FUNC || !sym->st_value) {
return NULL;
}
return laddr(base, sym->st_value);
}
// Entry point. Arguments are a handle to the bootstrap channel and the base
// address that the vDSO was loaded at.
void _start(zx_handle_t bootstrap_chan, void* vdso_base) {
Elf64_Ehdr* ehdr = (Elf64_Ehdr*)vdso_base;
// Find PT_DYNAMIC header.
const Elf64_Phdr* phdr = laddr(vdso_base, ehdr->e_phoff);
const Elf64_Phdr* phdr_dynamic = NULL;
for (Elf64_Half ph = 0; ph < ehdr->e_phnum; ++ph) {
if (phdr[ph].p_type == PT_DYNAMIC) {
phdr_dynamic = &phdr[ph];
}
}
if (!phdr_dynamic) {
return;
}
// Find the GNU hash table, symbol table, and string table.
const Elf64_Dyn* dyn_array = laddr(vdso_base, phdr_dynamic->p_vaddr);
const Elf64_Dyn* dyn_gnu_hash = search_dyn(dyn_array, DT_GNU_HASH);
const Elf64_Dyn* dyn_symtab = search_dyn(dyn_array, DT_SYMTAB);
const Elf64_Dyn* dyn_strtab = search_dyn(dyn_array, DT_STRTAB);
if (!dyn_gnu_hash || !dyn_symtab || !dyn_strtab) {
return;
}
const GnuHashTable hashtab =
read_gnu_hash_table(laddr(vdso_base, dyn_gnu_hash->d_un.d_val));
const Elf64_Sym* symtab = laddr(vdso_base, dyn_symtab->d_un.d_val);
const char* strtab = laddr(vdso_base, dyn_strtab->d_un.d_val);
// Lookup the channel_read and channel_write syscalls from the vDSO
zx_channel_read_t zx_channel_read =
lookup_func("_zx_channel_read", &hashtab, symtab, strtab, vdso_base);
zx_channel_write_t zx_channel_write =
lookup_func("_zx_channel_write", &hashtab, symtab, strtab, vdso_base);
if (!zx_channel_read || !zx_channel_write) {
return;
}
// Read the bootstrap message from the bootstrap channel and find the PA_USER0
// channel handle.
uint8_t read_msg[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t read_handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes, actual_handles;
if (zx_channel_read(bootstrap_chan, 0, read_msg, read_handles,
ARRAY_SIZE(read_msg), ARRAY_SIZE(read_handles),
&actual_bytes, &actual_handles) != ZX_OK) {
return;
}
zx_proc_args_t* bootstrap_header = (zx_proc_args_t*)read_msg;
uint32_t* handle_info =
(uint32_t*)((uint8_t*)read_msg + bootstrap_header->handle_info_off);
zx_handle_t user_chan = ZX_HANDLE_INVALID;
for (uint32_t i = 0; i < actual_handles; ++i) {
if (handle_info[i] == PA_HND(PA_USER0, 0)) {
user_chan = read_handles[i];
break;
}
}
if (user_chan == ZX_HANDLE_INVALID) {
return;
}
// Read a message from the PA_USER0 channel and echo it back. Note that
// ZX_ERR_SHOULD_WAIT isn't handled here; the test should make sure to write
// to the channel before starting us.
if (zx_channel_read(user_chan, 0, read_msg, read_handles,
ARRAY_SIZE(read_msg), ARRAY_SIZE(read_handles),
&actual_bytes, &actual_handles) != ZX_OK) {
return;
}
zx_channel_write(user_chan, 0, read_msg, actual_bytes, read_handles,
actual_handles);
// Exit cleanly.
zx_process_exit_t zx_process_exit =
lookup_func("_zx_process_exit", &hashtab, symtab, strtab, vdso_base);
zx_process_exit(0);
}
// Compiler emits calls to these so need to provide implementations.
void __stack_chk_fail(void) { __builtin_trap(); }
// From //zircon/third_party/ulib/musl/src/string/memset.c
void* memset(void* dest, int c, size_t n) {
unsigned char* s = dest;
size_t k;
/* Fill head and tail with minimal branching. Each
* conditional ensures that all the subsequently used
* offsets are well-defined and in the dest region. */
if (!n)
return dest;
s[0] = s[n - 1] = c;
if (n <= 2)
return dest;
s[1] = s[n - 2] = c;
s[2] = s[n - 3] = c;
if (n <= 6)
return dest;
s[3] = s[n - 4] = c;
if (n <= 8)
return dest;
/* Advance pointer to align it at a 4-byte boundary,
* and truncate n to a multiple of 4. The previous code
* already took care of any head/tail that get cut off
* by the alignment. */
k = -(uintptr_t)s & 3;
s += k;
n -= k;
n &= -4;
/* Pure C fallback with no aliasing violations. */
for (; n; n--, s++)
*s = c;
return dest;
}
// From //zircon/third_party/ulib/musl/src/string/memcpy.c
void* memcpy(void* restrict dest, const void* restrict src, size_t n) {
unsigned char* d = dest;
const unsigned char* s = src;
for (; n; n--)
*d++ = *s++;
return dest;
}