lib/elf: Validate file bounds in relocation engine and vaddr translation

This commit is contained in:
Mintsuki
2026-04-01 13:36:26 +02:00
parent 8118e60ddb
commit f4ce0420c0

View File

@@ -261,7 +261,35 @@ static bool elf64_is_relocatable(uint8_t *elf, struct elf64_hdr *hdr) {
panic(true, "elf: ELF file type is ET_DYN, but PT_DYNAMIC segment missing");
}
static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *buffer, uint64_t vaddr, size_t size, uint64_t slide) {
// Translate a virtual address to a file offset using the phdr table.
// Returns false if the vaddr is not found in any PT_LOAD segment or the
// translated offset exceeds file bounds.
static bool elf64_translate_vaddr(uint8_t *elf, size_t file_size,
struct elf64_hdr *hdr, uint64_t *offset, uint64_t size_hint,
uint64_t *out_seg_size) {
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
uint64_t seg_end = CHECKED_ADD(phdr->p_vaddr, phdr->p_filesz, continue);
if (phdr->p_vaddr <= *offset && seg_end > *offset) {
if (out_seg_size != NULL) {
*out_seg_size = phdr->p_filesz - (*offset - phdr->p_vaddr);
}
*offset -= phdr->p_vaddr;
*offset += phdr->p_offset;
// Validate translated offset + size_hint is within file
if (CHECKED_ADD(*offset, size_hint, return false) > file_size) {
return false;
}
return true;
}
}
return false;
}
static bool elf64_apply_relocations(uint8_t *elf, size_t file_size, struct elf64_hdr *hdr, void *buffer, uint64_t vaddr, size_t size, uint64_t slide) {
if (hdr->phdr_size < sizeof(struct elf64_phdr)) {
panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)");
}
@@ -284,6 +312,12 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
uint64_t rela_size = 0;
uint64_t rela_ent = 0;
// Validate phdr table is within file bounds
if (CHECKED_ADD(hdr->phoff, CHECKED_MUL((uint64_t)hdr->ph_num, (uint64_t)hdr->phdr_size,
return false), return false) > file_size) {
return false;
}
// Find DYN segment
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
@@ -291,7 +325,12 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
if (phdr->p_type != PT_DYNAMIC)
continue;
for (uint16_t j = 0; j < phdr->p_filesz / sizeof(struct elf64_dyn); j++) {
// Validate PT_DYNAMIC segment is within file bounds
if (CHECKED_ADD(phdr->p_offset, phdr->p_filesz, return false) > file_size) {
return false;
}
for (uint64_t j = 0; j < phdr->p_filesz / sizeof(struct elf64_dyn); j++) {
struct elf64_dyn *dyn = (void *)elf + (phdr->p_offset + j * sizeof(struct elf64_dyn));
switch (dyn->d_tag) {
@@ -346,91 +385,32 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
end_of_pt_segment:
if (rela_offset != 0) {
bool rela_translated = false;
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
if (_phdr->p_vaddr <= rela_offset && _phdr->p_vaddr + _phdr->p_filesz > rela_offset) {
rela_offset -= _phdr->p_vaddr;
rela_offset += _phdr->p_offset;
rela_translated = true;
break;
}
}
if (!rela_translated) {
panic(true, "elf: RELA vaddr not in any PT_LOAD segment");
if (!elf64_translate_vaddr(elf, file_size, hdr, &rela_offset, rela_size, NULL)) {
panic(true, "elf: RELA vaddr translation failed or out of bounds");
}
}
if (relr_offset != 0) {
bool relr_translated = false;
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
if (_phdr->p_vaddr <= relr_offset && _phdr->p_vaddr + _phdr->p_filesz > relr_offset) {
relr_offset -= _phdr->p_vaddr;
relr_offset += _phdr->p_offset;
relr_translated = true;
break;
}
}
if (!relr_translated) {
panic(true, "elf: RELR vaddr not in any PT_LOAD segment");
if (!elf64_translate_vaddr(elf, file_size, hdr, &relr_offset, relr_size, NULL)) {
panic(true, "elf: RELR vaddr translation failed or out of bounds");
}
}
if (symtab_offset != 0) {
bool symtab_translated = false;
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
if (_phdr->p_vaddr <= symtab_offset && _phdr->p_vaddr + _phdr->p_filesz > symtab_offset) {
// Calculate size as remaining bytes in segment from symtab start
symtab_size = _phdr->p_filesz - (symtab_offset - _phdr->p_vaddr);
symtab_offset -= _phdr->p_vaddr;
symtab_offset += _phdr->p_offset;
symtab_translated = true;
break;
}
}
if (!symtab_translated) {
panic(true, "elf: SYMTAB vaddr not in any PT_LOAD segment");
if (!elf64_translate_vaddr(elf, file_size, hdr, &symtab_offset, 0, &symtab_size)) {
panic(true, "elf: SYMTAB vaddr translation failed or out of bounds");
}
}
if (strtab_offset != 0) {
bool strtab_translated = false;
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
if (_phdr->p_vaddr <= strtab_offset && _phdr->p_vaddr + _phdr->p_filesz > strtab_offset) {
// Calculate size as remaining bytes in segment from strtab start
strtab_size = _phdr->p_filesz - (strtab_offset - _phdr->p_vaddr);
strtab_offset -= _phdr->p_vaddr;
strtab_offset += _phdr->p_offset;
strtab_translated = true;
break;
}
}
if (!strtab_translated) {
panic(true, "elf: STRTAB vaddr not in any PT_LOAD segment");
if (!elf64_translate_vaddr(elf, file_size, hdr, &strtab_offset, 0, &strtab_size)) {
panic(true, "elf: STRTAB vaddr translation failed or out of bounds");
}
}
if (dt_jmprel != 0) {
bool jmprel_translated = false;
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
if (_phdr->p_vaddr <= dt_jmprel && _phdr->p_vaddr + _phdr->p_filesz > dt_jmprel) {
dt_jmprel -= _phdr->p_vaddr;
dt_jmprel += _phdr->p_offset;
jmprel_translated = true;
break;
}
}
if (!jmprel_translated) {
panic(true, "elf: JMPREL vaddr not in any PT_LOAD segment");
if (!elf64_translate_vaddr(elf, file_size, hdr, &dt_jmprel, dt_pltrelsz, NULL)) {
panic(true, "elf: JMPREL vaddr translation failed or out of bounds");
}
}
@@ -668,7 +648,9 @@ bool elf64_load_section(uint8_t *elf, size_t file_size, void *buffer, const char
}
// Validate section header table is within file bounds
uint64_t shdr_table_end = (uint64_t)hdr->shoff + (uint64_t)hdr->sh_num * hdr->shdr_size;
uint64_t shdr_table_end = CHECKED_ADD(hdr->shoff,
CHECKED_MUL((uint64_t)hdr->sh_num, (uint64_t)hdr->shdr_size, return false),
return false);
if (shdr_table_end > file_size) {
return false;
}
@@ -710,7 +692,7 @@ bool elf64_load_section(uint8_t *elf, size_t file_size, void *buffer, const char
return false;
}
memcpy(buffer, elf + section->sh_offset, section->sh_size);
return elf64_apply_relocations(elf, hdr, buffer, section->sh_addr, section->sh_size, slide);
return elf64_apply_relocations(elf, file_size, hdr, buffer, section->sh_addr, section->sh_size, slide);
}
}
@@ -1014,7 +996,7 @@ again:
bss_size = phdr->p_memsz - phdr->p_filesz;
}
if (!elf64_apply_relocations(elf, hdr, (void *)(uintptr_t)load_addr, phdr->p_vaddr, phdr->p_memsz, slide)) {
if (!elf64_apply_relocations(elf, file_size, hdr, (void *)(uintptr_t)load_addr, phdr->p_vaddr, phdr->p_memsz, slide)) {
panic(true, "elf: Failed to apply relocations");
}