| // 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; |
| } |