Skip to content

Commit d795ca5

Browse files
committed
Add initial support for static PIE executables
1 parent def247e commit d795ca5

File tree

6 files changed

+111
-61
lines changed

6 files changed

+111
-61
lines changed

lib/tinykvm/amd64/paging.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ uint64_t setup_amd64_paging(vMemory& memory,
153153
if (hdr->p_type == PT_LOAD)
154154
{
155155
const size_t len = hdr->p_filesz;
156-
if (!memory.safely_within(hdr->p_vaddr, len)) {
156+
const uint64_t load_address = memory.machine.image_base() + hdr->p_vaddr;
157+
if (!memory.safely_within(load_address, len)) {
157158
throw MachineException("Unsafe PT_LOAD segment or executable too big");
158159
}
159160
const bool read = (hdr->p_flags & PF_R) != 0;
@@ -162,11 +163,11 @@ uint64_t setup_amd64_paging(vMemory& memory,
162163

163164
/* TODO: Prevent extremely high addresses */
164165
/* XXX: Prevent crossing gigabyte boundries */
165-
auto base = hdr->p_vaddr & ~0xFFFLL;
166-
auto end = ((hdr->p_vaddr + len) + 0xFFFLL) & ~0xFFFLL;
166+
auto base = load_address & ~0xFFFLL;
167+
auto end = ((load_address + len) + 0xFFFLL) & ~0xFFFLL;
167168
#if 0
168169
printf("0x%lX->0x%lX --> 0x%lX:0x%lX\n",
169-
hdr->p_vaddr, hdr->p_vaddr + len, base, end);
170+
load_address, load_address + len, base, end);
170171
#endif
171172
for (size_t addr = base; addr < end;)
172173
{

lib/tinykvm/machine.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Machine::Machine(const Machine& other, const MachineOptions& options)
6262
m_allow_fixed_mmap {options.allow_fixed_mmap},
6363
m_binary {options.binary.empty() ? other.m_binary : options.binary},
6464
memory {*this, options, other.memory},
65+
m_image_base {other.m_image_base},
6566
m_stack_address {other.m_stack_address},
6667
m_heap_address {other.m_heap_address},
6768
m_start_address {other.m_start_address},
@@ -139,6 +140,7 @@ void Machine::reset_to(const Machine& other, const MachineOptions& options)
139140
/* This could be dangerous, but we will allow it anyway,
140141
for those who dare to mutate an existing VM in prod. */
141142
this->m_binary = other.m_binary;
143+
this->m_image_base = other.m_image_base;
142144
this->m_stack_address = other.m_stack_address;
143145
this->m_heap_address = other.m_heap_address;
144146
this->m_start_address = other.m_start_address;

lib/tinykvm/machine.hpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct Machine
2020
using numbered_syscall_t = void(*)(vCPU&, unsigned);
2121
using io_callback_t = void(*)(vCPU&, unsigned, unsigned);
2222
using printer_func = std::function<void(const char*, size_t)>;
23+
static constexpr address_t DYLINK_BASE = 0x200000; // Dynamic link base address
2324

2425
/* Setup Linux env and run through main */
2526
void setup_argv(const std::vector<std::string>& args,
@@ -129,6 +130,8 @@ struct Machine
129130
char* unsafe_memory_at(uint64_t a, size_t s) { return memory.at(a, s); }
130131
uint64_t translate(uint64_t virt) const;
131132

133+
bool is_dynamic() const noexcept { return m_image_base != 0x0; }
134+
address_t image_base() const noexcept { return this->m_image_base; }
132135
address_t start_address() const noexcept { return this->m_start_address; }
133136
address_t stack_address() const noexcept { return this->m_stack_address; }
134137
address_t heap_address() const noexcept { return this->m_heap_address; }
@@ -220,7 +223,8 @@ struct Machine
220223
void setup_linux(__u64&, const std::vector<std::string>&, const std::vector<std::string>&);
221224
void elf_loader(std::string_view binary, const MachineOptions&);
222225
void elf_load_ph(std::string_view binary, const MachineOptions&, const void*);
223-
void relocate_section(const char* section_name, const char* sym_section);
226+
void dynamic_linking(std::string_view binary, const MachineOptions&);
227+
bool relocate_section(const char* section_name, const char* sym_section);
224228
void setup_long_mode(const MachineOptions&);
225229
void setup_cow_mode(const Machine*); // After prepare_copy_on_write and forking
226230
[[noreturn]] static void machine_exception(const char*, uint64_t = 0);
@@ -240,12 +244,13 @@ struct Machine
240244

241245
vMemory memory; // guest memory
242246

243-
uint64_t m_stack_address;
244-
uint64_t m_heap_address;
245-
uint64_t m_start_address;
246-
uint64_t m_kernel_end;
247+
address_t m_image_base = 0x0;
248+
address_t m_stack_address;
249+
address_t m_heap_address;
250+
address_t m_start_address;
251+
address_t m_kernel_end;
247252

248-
uint64_t m_mm = 0;
253+
address_t m_mm = 0;
249254
MMapCache m_mmap_cache;
250255
mutable std::unique_ptr<MultiThreading> m_mt;
251256

lib/tinykvm/machine_elf.cpp

Lines changed: 80 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,24 @@ void Machine::elf_loader(std::string_view binary, const MachineOptions& options)
2121
if (UNLIKELY(!validate_header(elf))) {
2222
throw MachineException("Invalid ELF header! Not a 64-bit program?");
2323
}
24-
if (UNLIKELY(elf->e_type != ET_EXEC)) {
25-
throw MachineException("Invalid ELF type: Not an executable!");
24+
bool is_dynamic = false;
25+
if (elf->e_type == ET_DYN) {
26+
is_dynamic = true;
27+
this->m_image_base = DYLINK_BASE;
28+
}
29+
else if (elf->e_type == ET_EXEC) {
30+
this->m_image_base = 0x0;
31+
} else {
32+
throw MachineException("Invalid ELF type: Not a static or dynamic executable!");
2633
}
2734

2835
// enumerate & load loadable segments
2936
const auto program_headers = elf->e_phnum;
3037
if (UNLIKELY(program_headers <= 0)) {
3138
throw MachineException("ELF with no program-headers");
3239
}
33-
if (UNLIKELY(program_headers >= 16)) {
34-
throw MachineException("ELF with too many program-headers. Dynamic?");
40+
if (UNLIKELY(program_headers >= 64)) {
41+
throw MachineException("ELF with too many program-headers");
3542
}
3643
if (UNLIKELY(elf->e_phoff + program_headers * sizeof(Elf64_Phdr) > binary.size())) {
3744
throw MachineException("ELF program-headers are outside the binary");
@@ -48,8 +55,8 @@ void Machine::elf_loader(std::string_view binary, const MachineOptions& options)
4855

4956
const auto* phdr = (Elf64_Phdr*) (binary.data() + elf->e_phoff);
5057
const auto program_begin = phdr->p_vaddr;
51-
this->m_start_address = elf->e_entry;
52-
this->m_stack_address = program_begin;
58+
this->m_start_address = this->m_image_base + elf->e_entry;
59+
this->m_stack_address = this->m_image_base + program_begin;
5360
this->m_heap_address = 0x0;
5461

5562
int seg = 0;
@@ -61,13 +68,15 @@ void Machine::elf_loader(std::string_view binary, const MachineOptions& options)
6168
}
6269

6370
// Detect overlapping segments
64-
for (const auto* ph = phdr; ph < hdr; ph++) {
65-
if (hdr->p_type == PT_LOAD && ph->p_type == PT_LOAD)
66-
if (ph->p_vaddr < hdr->p_vaddr + hdr->p_filesz &&
67-
ph->p_vaddr + ph->p_filesz >= hdr->p_vaddr) {
68-
// Normally we would not care, but no normal ELF
69-
// has overlapping segments, so treat as bogus.
70-
throw MachineException("Overlapping ELF segments");
71+
if (hdr->p_type == PT_LOAD) {
72+
for (const auto* ph = phdr; ph < hdr; ph++) {
73+
if (ph->p_type == PT_LOAD &&
74+
ph->p_vaddr < hdr->p_vaddr + hdr->p_filesz &&
75+
ph->p_vaddr + ph->p_filesz >= hdr->p_vaddr) {
76+
// Normally we would not care, but no normal ELF
77+
// has overlapping segments, so treat as bogus.
78+
throw MachineException("Overlapping ELF segments", hdr->p_vaddr);
79+
}
7180
}
7281
}
7382

@@ -96,6 +105,7 @@ void Machine::elf_loader(std::string_view binary, const MachineOptions& options)
96105
}
97106

98107
/* Make sure mmap starts at a sane offset */
108+
this->m_heap_address += this->m_image_base;
99109
this->m_mm = this->mmap_start();
100110

101111
/* If there is not enough room for stack, move it */
@@ -104,7 +114,10 @@ void Machine::elf_loader(std::string_view binary, const MachineOptions& options)
104114
this->m_stack_address = this->mmap_allocate(STACK_SIZE) + STACK_SIZE;
105115
}
106116

107-
//this->relocate_section(".rela.plt", ".symtab");
117+
/* Dynamic executables require some extra work, like relocation */
118+
if (is_dynamic) {
119+
this->dynamic_linking(binary, options);
120+
}
108121

109122
if (options.verbose_loader) {
110123
printf("* Entry is at %p\n", (void*) m_start_address);
@@ -127,19 +140,28 @@ void Machine::elf_load_ph(std::string_view binary, const MachineOptions& options
127140
if (binary.size() < hdr->p_offset + len) {
128141
throw MachineException("Not enough room for ELF program segment", len);
129142
}
130-
if (hdr->p_vaddr + len < hdr->p_vaddr) {
143+
const address_t load_address = this->m_image_base + hdr->p_vaddr;
144+
if (load_address + len < load_address) {
131145
throw MachineException("Bogus ELF segment virtual base", hdr->p_vaddr);
132146
}
133147

134148
if (options.verbose_loader) {
135149
printf("* Loading segment of size %zu from %p to virtual %p\n",
136-
len, src, (void*) hdr->p_vaddr);
150+
len, src, (void*) load_address);
137151
}
138152

139-
if (memory.safely_within(hdr->p_vaddr, len)) {
140-
std::memcpy(memory.at(hdr->p_vaddr), src, len);
153+
if (UNLIKELY(load_address < this->m_image_base)) {
154+
throw MachineException("Bogus ELF segment virtual base", hdr->p_vaddr);
155+
}
156+
if (memory.safely_within(load_address, len)) {
157+
std::memcpy(memory.at(load_address), src, len);
141158
} else {
142-
throw MachineException("Unsafe PT_LOAD segment or executable too big", hdr->p_vaddr);
159+
if (options.verbose_loader) {
160+
printf("Segment at %p is too large or not safely within physical base at %p. Size: %zu vs %p\n",
161+
(void*)load_address, (void*)memory.safebase, len, (void*)(memory.physbase + memory.size));
162+
fflush(stdout);
163+
}
164+
throw MachineException("Unsafe PT_LOAD segment or executable too big", load_address);
143165
}
144166
}
145167

@@ -190,7 +212,7 @@ static const Elf64_Sym* resolve_symbol(std::string_view binary, const char* name
190212
uint64_t Machine::address_of(const char* name) const
191213
{
192214
const auto* sym = resolve_symbol(m_binary, name);
193-
return (sym) ? sym->st_value : 0x0;
215+
return (sym) ? this->m_image_base + sym->st_value : 0x0;
194216
}
195217
std::string Machine::resolve(uint64_t rip) const
196218
{
@@ -200,6 +222,9 @@ std::string Machine::resolve(uint64_t rip) const
200222
const auto* str_hdr = section_by_name(m_binary, ".strtab");
201223
if (UNLIKELY(str_hdr == nullptr)) return "";
202224

225+
if (UNLIKELY(rip < this->m_image_base)) return "";
226+
const address_t relative_rip = rip - this->m_image_base;
227+
203228
const auto* symtab = elf_sym_index(m_binary, sym_hdr, 0);
204229
const size_t symtab_ents = sym_hdr->sh_size / sizeof(Elf64_Sym);
205230
const char* strtab = elf_offset<char>(m_binary, str_hdr->sh_offset);
@@ -209,9 +234,9 @@ std::string Machine::resolve(uint64_t rip) const
209234
/* Only look at functions (for now). Old-style symbols have no FUNC. */
210235
if (symtab[i].st_info & STT_FUNC) {
211236
/* Direct matches only (for now) */
212-
if (rip >= symtab[i].st_value && rip < symtab[i].st_value + symtab[i].st_size)
237+
if (relative_rip >= symtab[i].st_value && relative_rip < symtab[i].st_value + symtab[i].st_size)
213238
{
214-
const uint64_t offset = rip - symtab[i].st_value;
239+
const uint64_t offset = relative_rip - symtab[i].st_value;
215240
char result[2048];
216241
int len = snprintf(result, sizeof(result),
217242
"%s + 0x%lX", &strtab[symtab[i].st_name], offset);
@@ -225,49 +250,55 @@ std::string Machine::resolve(uint64_t rip) const
225250
return "(unknown)";
226251
}
227252

228-
void Machine::relocate_section(const char* section_name, const char* sym_section)
253+
bool Machine::relocate_section(const char* section_name, const char* sym_section)
229254
{
230255
const auto* rela = section_by_name(m_binary, section_name);
231256
if (rela == nullptr) {
232257
printf("No such section: %s\n", section_name);
233-
return;
258+
return false;
234259
}
235260
const auto* dyn_hdr = section_by_name(m_binary, sym_section);
236261
if (dyn_hdr == nullptr) {
237262
printf("No such symbol section: %s\n", sym_section);
238-
return;
263+
return false;
264+
}
265+
const size_t rela_ents = rela->sh_size / sizeof(Elf64_Rela);
266+
if (rela_ents > 16384) {
267+
throw MachineException("Too many relocations", rela_ents);
239268
}
240-
const size_t rela_ents = rela->sh_size / rela->sh_entsize;
241-
printf("Rela ents: %zu\n", rela_ents);
242269

243-
auto* rela_addr = elf_offset<Elf64_Rela>(m_binary, rela->sh_offset);
270+
auto* rela_addr = elf_offset_array<Elf64_Rela>(m_binary, rela->sh_offset, rela_ents);
244271
for (size_t i = 0; i < rela_ents; i++)
245272
{
246-
const uint8_t type = ELF64_R_TYPE(rela_addr[i].r_info);
247-
if (type == R_X86_64_IRELATIVE)
248-
{
249-
const uint32_t symidx = ELF64_R_SYM(rela_addr[i].r_info);
250-
//auto* sym = elf_sym_index(m_binary, dyn_hdr, symidx);
251-
const int32_t addend = rela_addr[i].r_addend;
252-
const uint64_t addr = rela_addr[i].r_offset;
253-
printf("Rela ent %zu with addend 0x%X = 0x%lX\n", i, addend, addr);
254-
auto* entry = (address_t*) memory.at(addend, 8);
255-
#ifdef TINYKVM_ARCH_AMD64
256-
*entry = interrupt_header().vm64_dso;
257-
#endif
273+
const auto symidx = ELF64_R_SYM(rela_addr[i].r_info);
274+
const Elf64_Sym* sym = elf_sym_index(m_binary, dyn_hdr, symidx);
258275

259-
/* auto* entry = elf_offset<address_t> (m_binary, rela_addr[i].r_offset);
260-
auto* final = elf_offset<address_t> (m_binary, sym->st_value);
261-
if constexpr (true)
262-
{
263-
printf("Relocating rela %zu with sym idx %u where 0x%lX -> 0x%lX\n",
264-
i, symidx, rela_addr[i].r_offset, sym->st_value);
276+
const auto rtype = ELF64_R_TYPE(rela_addr[i].r_info);
277+
if (rtype != R_X86_64_RELATIVE) {
278+
if constexpr (VERBOSE_LOADER) {
279+
printf("Skipping non-relative relocation: %s\n", &m_binary[sym->st_name]);
280+
}
281+
continue;
282+
}
283+
284+
const address_t addr = this->m_image_base + rela_addr[i].r_offset;
285+
if (memory.safely_within(addr, sizeof(address_t))) {
286+
*(address_t*) memory.safely_at(addr, sizeof(address_t)) = this->m_image_base + sym->st_value;
287+
} else if (false) {
288+
if constexpr (VERBOSE_LOADER) {
289+
printf("Relocation failed: %s\n", &m_binary[sym->st_name]);
265290
}
266-
// *(address_t*) entry = (address_t) (uintptr_t) final;
267-
*(address_t*) entry = interrupt_header().vm64_gettimeofday;
268-
*/
269291
}
270292
}
293+
return true;
294+
}
295+
296+
void Machine::dynamic_linking(std::string_view binary, const MachineOptions& options)
297+
{
298+
(void)binary;
299+
(void)options;
300+
this->relocate_section(".rela.dyn", ".dynsym");
301+
//this->relocate_section(".rela.plt", ".dynsym");
271302
}
272303

273304
}

lib/tinykvm/machine_env.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,10 @@ void Machine::setup_linux(__u64& rsp,
125125
push_aux(argv, {AT_PHNUM, phdr_count});
126126

127127
// Misc
128-
push_aux(argv, {AT_BASE, binary_ehdr->e_entry & ~0xFFFFFFL}); // XXX: Guesstimate!
129-
push_aux(argv, {AT_ENTRY, binary_ehdr->e_entry});
128+
const address_t base_address = (this->m_image_base + binary_ehdr->e_entry) & ~0xFFFFFFL; // XXX: Guesstimate!
129+
const address_t entry_address = this->m_image_base + binary_ehdr->e_entry;
130+
push_aux(argv, {AT_BASE, base_address});
131+
push_aux(argv, {AT_ENTRY, entry_address});
130132
push_aux(argv, {AT_HWCAP, getauxval(AT_HWCAP)});
131133
push_aux(argv, {AT_HWCAP2, getauxval(AT_HWCAP2)});
132134
push_aux(argv, {AT_UID, 1000});

lib/tinykvm/util/elf.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
#pragma once
2+
#include "../common.hpp" // for MachineException
23
#include "elf.h"
34

45
namespace tinykvm {
56

67
template <typename T>
78
inline const T* elf_offset(std::string_view binary, intptr_t ofs) {
9+
if (ofs < 0 || ofs + sizeof(T) > binary.size())
10+
throw MachineException("Invalid ELF offset", ofs);
11+
return (const T*) &binary.at(ofs);
12+
}
13+
template <typename T>
14+
inline const T* elf_offset_array(std::string_view binary, intptr_t ofs, size_t count) {
15+
if (ofs < 0 || ofs + count * sizeof(T) > binary.size())
16+
throw MachineException("Invalid ELF offset", ofs);
817
return (const T*) &binary.at(ofs);
918
}
1019
inline const auto* elf_header(std::string_view binary) {

0 commit comments

Comments
 (0)