GPLv2 release
[centaur.git] / src / libelfu / elfops / reorderphdrs.c
1 /* This file is part of centaur.
2  *
3  * centaur is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License 2 as
5  * published by the Free Software Foundation.
6
7  * centaur is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11
12  * You should have received a copy of the GNU General Public License
13  * along with centaur.  If not, see <http://www.gnu.org/licenses/>.
14  */
15
16 #include <assert.h>
17 #include <stdlib.h>
18 #include <libelfu/libelfu.h>
19
20
21 static int cmpPhdrType(const void *v1, const void *v2)
22 {
23   const GElf_Phdr *p1 = (const GElf_Phdr*)v1;
24   const GElf_Phdr *p2 = (const GElf_Phdr*)v2;
25
26   /* These entries go first */
27   if (p1->p_type == PT_PHDR) {
28     return -1;
29   } else if (p2->p_type == PT_PHDR) {
30     return 1;
31   } else if (p1->p_type == PT_INTERP) {
32     return -1;
33   } else if (p2->p_type == PT_INTERP) {
34     return 1;
35   }
36
37   /* These entries go last */
38   if (p1->p_type == PT_GNU_RELRO) {
39     return 1;
40   } else if (p2->p_type == PT_GNU_RELRO) {
41     return -1;
42   } else if (p1->p_type == PT_GNU_STACK) {
43     return 1;
44   } else if (p2->p_type == PT_GNU_STACK) {
45     return -1;
46   } else if (p1->p_type == PT_GNU_EH_FRAME) {
47     return 1;
48   } else if (p2->p_type == PT_GNU_EH_FRAME) {
49     return -1;
50   } else if (p1->p_type == PT_NOTE) {
51     return 1;
52   } else if (p2->p_type == PT_NOTE) {
53     return -1;
54   } else if (p1->p_type == PT_DYNAMIC) {
55     return 1;
56   } else if (p2->p_type == PT_DYNAMIC) {
57     return -1;
58   }
59
60   /* Sort the rest by vaddr. */
61   if (p1->p_vaddr < p2->p_vaddr) {
62     return -1;
63   } else if (p1->p_vaddr > p2->p_vaddr) {
64     return 1;
65   }
66
67   /* Everything else is equal */
68   return 0;
69 }
70
71
72
73 /* The ELF specification wants PHDR and INTERP headers to come first.
74  * Typical GNU programs also contain DYNAMIC, NOTE, and GNU_* after
75  * the LOAD segments.
76  * Reorder them to comply with the spec. Otherwise the dynamic linker
77  * will calculate the base address incorrectly and crash.
78  *
79  * Both Linux and glibc's ld.so are unhelpful here:
80  * Linux calculates the PH table address as
81  *     phdrs = (base + e_phoff)
82  * and passes that to ld.so.
83  * ld.so 'recovers' the base address as
84  *     base = (phdrs - PT_PHDR.p_vaddr)
85  *
86  * Behold those who try to move the PHDR table away from the first
87  * page of memory, where it is stored at the address calculated by
88  * Linux. Things will crash. Badly.
89  *
90  * Unfortunately the ELF spec itself states that the PHT is in the
91  * first page of memory. Not much we can do here.
92  */
93 void elfu_eReorderPhdrs(Elf *e)
94 {
95   size_t i, numPhdr;
96
97   assert(e);
98
99   assert(!elf_getphdrnum(e, &numPhdr));
100
101   GElf_Phdr phdrs[numPhdr];
102
103   for (i = 0; i < numPhdr; i++) {
104     GElf_Phdr phdr;
105
106     if (gelf_getphdr(e, i, &phdr) != &phdr) {
107       ELFU_WARN("gelf_getphdr() failed for #%d: %s\n", i, elf_errmsg(-1));
108       return;
109     }
110
111     phdrs[i] = phdr;
112   }
113
114   qsort(phdrs, numPhdr, sizeof(*phdrs), cmpPhdrType);
115
116   for (i = 0; i < numPhdr; i++) {
117     assert(gelf_update_phdr(e, i, &phdrs[i]));
118   }
119 }