/* This file is part of centaur.
*
* centaur is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License 2 as
* published by the Free Software Foundation.
* centaur is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with centaur. If not, see .
*/
#include
#include
#include
static int cmpPhdrType(const void *v1, const void *v2)
{
const GElf_Phdr *p1 = (const GElf_Phdr*)v1;
const GElf_Phdr *p2 = (const GElf_Phdr*)v2;
/* These entries go first */
if (p1->p_type == PT_PHDR) {
return -1;
} else if (p2->p_type == PT_PHDR) {
return 1;
} else if (p1->p_type == PT_INTERP) {
return -1;
} else if (p2->p_type == PT_INTERP) {
return 1;
}
/* These entries go last */
if (p1->p_type == PT_GNU_RELRO) {
return 1;
} else if (p2->p_type == PT_GNU_RELRO) {
return -1;
} else if (p1->p_type == PT_GNU_STACK) {
return 1;
} else if (p2->p_type == PT_GNU_STACK) {
return -1;
} else if (p1->p_type == PT_GNU_EH_FRAME) {
return 1;
} else if (p2->p_type == PT_GNU_EH_FRAME) {
return -1;
} else if (p1->p_type == PT_NOTE) {
return 1;
} else if (p2->p_type == PT_NOTE) {
return -1;
} else if (p1->p_type == PT_DYNAMIC) {
return 1;
} else if (p2->p_type == PT_DYNAMIC) {
return -1;
}
/* Sort the rest by vaddr. */
if (p1->p_vaddr < p2->p_vaddr) {
return -1;
} else if (p1->p_vaddr > p2->p_vaddr) {
return 1;
}
/* Everything else is equal */
return 0;
}
/* The ELF specification wants PHDR and INTERP headers to come first.
* Typical GNU programs also contain DYNAMIC, NOTE, and GNU_* after
* the LOAD segments.
* Reorder them to comply with the spec. Otherwise the dynamic linker
* will calculate the base address incorrectly and crash.
*
* Both Linux and glibc's ld.so are unhelpful here:
* Linux calculates the PH table address as
* phdrs = (base + e_phoff)
* and passes that to ld.so.
* ld.so 'recovers' the base address as
* base = (phdrs - PT_PHDR.p_vaddr)
*
* Behold those who try to move the PHDR table away from the first
* page of memory, where it is stored at the address calculated by
* Linux. Things will crash. Badly.
*
* Unfortunately the ELF spec itself states that the PHT is in the
* first page of memory. Not much we can do here.
*/
void elfu_eReorderPhdrs(Elf *e)
{
size_t i, numPhdr;
assert(e);
assert(!elf_getphdrnum(e, &numPhdr));
GElf_Phdr phdrs[numPhdr];
for (i = 0; i < numPhdr; i++) {
GElf_Phdr phdr;
if (gelf_getphdr(e, i, &phdr) != &phdr) {
ELFU_WARN("gelf_getphdr() failed for #%d: %s\n", i, elf_errmsg(-1));
return;
}
phdrs[i] = phdr;
}
qsort(phdrs, numPhdr, sizeof(*phdrs), cmpPhdrType);
for (i = 0; i < numPhdr; i++) {
assert(gelf_update_phdr(e, i, &phdrs[i]));
}
}