c2c92fde3811e3d3aac121861096b1646961ef98
[centaur.git] / src / libelfu / elfops / reorderphdrs.c
1 #include <assert.h>
2 #include <stdlib.h>
3 #include <libelfu/libelfu.h>
4
5
6 static int cmpPhdrType(const void *v1, const void *v2)
7 {
8   const GElf_Phdr *p1 = (const GElf_Phdr*)v1;
9   const GElf_Phdr *p2 = (const GElf_Phdr*)v2;
10
11   /* These entries go first */
12   if (p1->p_type == PT_PHDR) {
13     return -1;
14   } else if (p2->p_type == PT_PHDR) {
15     return 1;
16   } else if (p1->p_type == PT_INTERP) {
17     return -1;
18   } else if (p2->p_type == PT_INTERP) {
19     return 1;
20   }
21
22   /* These entries go last */
23   if (p1->p_type == PT_GNU_RELRO) {
24     return 1;
25   } else if (p2->p_type == PT_GNU_RELRO) {
26     return -1;
27   } else if (p1->p_type == PT_GNU_STACK) {
28     return 1;
29   } else if (p2->p_type == PT_GNU_STACK) {
30     return -1;
31   } else if (p1->p_type == PT_GNU_EH_FRAME) {
32     return 1;
33   } else if (p2->p_type == PT_GNU_EH_FRAME) {
34     return -1;
35   } else if (p1->p_type == PT_NOTE) {
36     return 1;
37   } else if (p2->p_type == PT_NOTE) {
38     return -1;
39   } else if (p1->p_type == PT_DYNAMIC) {
40     return 1;
41   } else if (p2->p_type == PT_DYNAMIC) {
42     return -1;
43   }
44
45   /* Sort the rest by vaddr. */
46   if (p1->p_vaddr < p2->p_vaddr) {
47     return -1;
48   } else if (p1->p_vaddr > p2->p_vaddr) {
49     return 1;
50   }
51
52   /* Everything else is equal */
53   return 0;
54 }
55
56
57
58 /* The ELF specification wants PHDR and INTERP headers to come first.
59  * Typical GNU programs also contain DYNAMIC, NOTE, and GNU_* after
60  * the LOAD segments.
61  * Reorder them to comply with the spec. Otherwise the dynamic linker
62  * will calculate the base address incorrectly and crash.
63  *
64  * Both Linux and glibc's ld.so are unhelpful here:
65  * Linux calculates the PH table address as
66  *     phdrs = (base + e_phoff)
67  * and passes that to ld.so.
68  * ld.so 'recovers' the base address as
69  *     base = (phdrs - PT_PHDR.p_vaddr)
70  *
71  * Behold those who try to move the PHDR table away from the first
72  * page of memory, where it is stored at the address calculated by
73  * Linux. Things will crash. Badly.
74  *
75  * Unfortunately the ELF spec itself states that the PHT is in the
76  * first page of memory. Not much we can do here.
77  */
78 void elfu_eReorderPhdrs(Elf *e)
79 {
80   size_t i, numPhdr;
81
82   assert(e);
83
84   assert(!elf_getphdrnum(e, &numPhdr));
85
86   GElf_Phdr phdrs[numPhdr];
87
88   for (i = 0; i < numPhdr; i++) {
89     GElf_Phdr phdr;
90
91     if (gelf_getphdr(e, i, &phdr) != &phdr) {
92       ELFU_WARN("gelf_getphdr() failed for #%d: %s\n", i, elf_errmsg(-1));
93       return;
94     }
95
96     phdrs[i] = phdr;
97   }
98
99   qsort(phdrs, numPhdr, sizeof(*phdrs), cmpPhdrType);
100
101   for (i = 0; i < numPhdr; i++) {
102     assert(gelf_update_phdr(e, i, &phdrs[i]));
103   }
104 }