121 lines
3.9 KiB
C
121 lines
3.9 KiB
C
// SPDX-License-Identifier: ISC
|
|
|
|
#include <bits/elf.h>
|
|
#include <string.h>
|
|
#include "loader.h"
|
|
|
|
static bool in_image(const struct kernel_image* image, uint64_t vaddr, uint64_t len) {
|
|
return vaddr >= image->virt && len <= image->size && vaddr - image->virt <= image->size - len;
|
|
}
|
|
|
|
void* kernel_ptr(const struct kernel_image* image, uint64_t vaddr) {
|
|
return loader_ptr(image->phys + (vaddr - image->virt));
|
|
}
|
|
|
|
static bool apply_relocations(const struct kernel_image* image, uint64_t dynamic_vaddr) {
|
|
if (!in_image(image, dynamic_vaddr, sizeof(elf64_dyn_t)))
|
|
return false;
|
|
|
|
uint64_t rela = 0;
|
|
uint64_t rela_size = 0;
|
|
uint64_t rela_ent = sizeof(elf64_rela_t);
|
|
for (const elf64_dyn_t* dyn = kernel_ptr(image, dynamic_vaddr); dyn->d_tag != DT_NULL; dyn++) {
|
|
switch (dyn->d_tag) {
|
|
case DT_RELA:
|
|
rela = dyn->d_un.d_val;
|
|
break;
|
|
case DT_RELASZ:
|
|
rela_size = dyn->d_un.d_val;
|
|
break;
|
|
case DT_RELAENT:
|
|
rela_ent = dyn->d_un.d_val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!rela)
|
|
return true;
|
|
if (rela_ent < sizeof(elf64_rela_t) || !in_image(image, rela, rela_size))
|
|
return false;
|
|
|
|
for (uint64_t off = 0; off + rela_ent <= rela_size; off += rela_ent) {
|
|
const elf64_rela_t* r = kernel_ptr(image, rela + off);
|
|
const uint32_t type = ELF64_R_TYPE(r->r_info);
|
|
if (type == R_NONE)
|
|
continue;
|
|
if (type != R_RELATIVE)
|
|
return false;
|
|
if (!in_image(image, r->r_offset, sizeof(uint64_t)))
|
|
return false;
|
|
*(uint64_t*)kernel_ptr(image, r->r_offset) = (uint64_t)r->r_addend;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool elf_load(const uint8_t* elf, size_t size, struct kernel_image* out) {
|
|
elf64_ehdr_t ehdr;
|
|
if (size < sizeof(ehdr))
|
|
return false;
|
|
memcpy(&ehdr, elf, sizeof(ehdr));
|
|
|
|
if (memcmp(ehdr.e_ident, ELF_MAG, sizeof(ELF_MAG)) || ehdr.e_ident[EI_CLASS] != ELF_ARCH_CLASS ||
|
|
ehdr.e_ident[EI_DATA] != ELF_ARCH_DATA)
|
|
return false;
|
|
if (ehdr.e_machine != ELF_ARCH_MACHINE)
|
|
return false;
|
|
if (ehdr.e_phentsize != sizeof(elf64_phdr_t) || ehdr.e_phoff > size ||
|
|
(size - ehdr.e_phoff) / sizeof(elf64_phdr_t) < ehdr.e_phnum)
|
|
return false;
|
|
|
|
const elf64_phdr_t* phdrs = (const elf64_phdr_t*)(elf + ehdr.e_phoff);
|
|
|
|
uint64_t lo = UINT64_MAX;
|
|
uint64_t hi = 0;
|
|
uint64_t dynamic_vaddr = 0;
|
|
uint64_t info_vaddr = 0;
|
|
uint64_t info_size = 0;
|
|
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
|
const elf64_phdr_t* ph = &phdrs[i];
|
|
if (ph->p_type == PT_DYNAMIC)
|
|
dynamic_vaddr = ph->p_vaddr;
|
|
if (ph->p_type == PT_BOOT_INFO) {
|
|
info_vaddr = ph->p_vaddr;
|
|
info_size = ph->p_memsz;
|
|
}
|
|
if (ph->p_type != PT_LOAD || ph->p_memsz == 0)
|
|
continue;
|
|
if (ph->p_filesz > ph->p_memsz || ph->p_offset > size || size - ph->p_offset < ph->p_filesz)
|
|
return false;
|
|
if (ALIGN_DOWN(ph->p_vaddr, PAGE_SIZE) < lo)
|
|
lo = ALIGN_DOWN(ph->p_vaddr, PAGE_SIZE);
|
|
if (ALIGN_UP(ph->p_vaddr + ph->p_memsz, PAGE_SIZE) > hi)
|
|
hi = ALIGN_UP(ph->p_vaddr + ph->p_memsz, PAGE_SIZE);
|
|
}
|
|
if (lo >= hi)
|
|
return false;
|
|
|
|
out->virt = lo;
|
|
out->size = hi - lo;
|
|
out->entry = ehdr.e_entry;
|
|
if (!in_image(out, out->entry, 1))
|
|
return false;
|
|
if (info_size != sizeof(struct boot_info) || !in_image(out, info_vaddr, info_size))
|
|
return false;
|
|
|
|
if (!loader_alloc(PHYS_STATIC, out->size, &out->phys))
|
|
return false;
|
|
|
|
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
|
const elf64_phdr_t* ph = &phdrs[i];
|
|
if (ph->p_type != PT_LOAD || ph->p_memsz == 0)
|
|
continue;
|
|
memcpy(kernel_ptr(out, ph->p_vaddr), elf + ph->p_offset, ph->p_filesz);
|
|
}
|
|
out->info = kernel_ptr(out, info_vaddr);
|
|
|
|
if (dynamic_vaddr)
|
|
return apply_relocations(out, dynamic_vaddr);
|
|
return true;
|
|
}
|