Fix alignment error in mInsertSpaceBefore
[centaur.git] / src / model / insert.c
1 #include <assert.h>
2 #include <sys/types.h>
3 #include <libelf/gelf.h>
4 #include <libelfu/libelfu.h>
5
6
7 // TODO: Take p_align into account
8
9
10
11 /*
12  * Insert space at a given position in the file by moving everything
13  * after it towards the end of the file, and everything before it
14  * towards lower memory regions where it is mapped.
15  *
16  * off must not be in the middle of any data structure, such as
17  * PHDRs, SHDRs, or sections. Behaviour is undefined if it is.
18  *
19  * PHDRs will be patched such that everything AFTER off is mapped to
20  * the same address in memory, and everything BEFORE it is shifted to
21  * lower addresses, making space for the new data in-between.
22  */
23 GElf_Xword elfu_mInsertSpaceBefore(ElfuElf *me, GElf_Off off, GElf_Xword size)
24 {
25   ElfuScn *ms;
26   ElfuPhdr *mp;
27
28   assert(me);
29
30   /* Round up size to 4096 bytes to keep page alignment on x86 when
31    * remapping existing data to lower addresses. */
32   size += (4096 - (size % 4096)) % 4096;
33   // TODO: Find alignment size by checking p_align in PHDRs
34
35   /* Move SHDRs and PHDRs */
36   if (me->ehdr.e_shoff >= off) {
37     me->ehdr.e_shoff += size;
38   }
39
40   if (me->ehdr.e_phoff >= off) {
41     me->ehdr.e_phoff += size;
42   }
43
44   /* Patch PHDRs to include new data */
45   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
46     GElf_Off end = mp->phdr.p_offset + mp->phdr.p_filesz;
47
48     if (mp->phdr.p_offset >= off) {
49       /* Insertion before PHDR's content, so it's just shifted */
50       mp->phdr.p_offset += size;
51     } else {
52       /* mp->phdr.p_offset < off */
53
54       if (off < end) {
55         /* Insertion in the middle of PHDR, so let it span the new data */
56         mp->phdr.p_filesz += size;
57         mp->phdr.p_memsz += size;
58         mp->phdr.p_vaddr -= size;
59         mp->phdr.p_paddr -= size;
60       } else {
61         /* Insertion after PHDR's content, so it may need to be
62            remapped. This will happen in a second pass.
63          */
64       }
65     }
66   }
67
68   /* For each LOAD header, find clashing headers that need to be
69      remapped to lower memory areas.
70    */
71   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
72     if (mp->phdr.p_type == PT_LOAD) {
73       ElfuPhdr *mp2;
74
75       CIRCLEQ_FOREACH(mp2, &me->phdrList, elem) {
76         if (mp2->phdr.p_type != PT_LOAD
77             && mp2->phdr.p_offset + mp2->phdr.p_filesz <= off) {
78           /* The PHDR ends in the file before the injection site */
79           GElf_Off vend1 = mp->phdr.p_vaddr + mp->phdr.p_memsz;
80           GElf_Off pend1 = mp->phdr.p_paddr + mp->phdr.p_memsz;
81           GElf_Off vend2 = mp2->phdr.p_vaddr + mp2->phdr.p_memsz;
82           GElf_Off pend2 = mp2->phdr.p_paddr + mp2->phdr.p_memsz;
83
84           /* If mp and mp2 now overlap in memory */
85           if ((mp2->phdr.p_vaddr < vend1 && vend2 > mp->phdr.p_vaddr)
86               || (mp2->phdr.p_paddr < pend1 && pend2 > mp->phdr.p_paddr)) {
87             /* Move mp2 down in memory, as mp has been resized.
88                Maintaining the relative offset between them is the best
89                guess at maintaining consistency.
90              */
91             mp2->phdr.p_vaddr -= size;
92             mp2->phdr.p_paddr -= size;
93           }
94         }
95       }
96     }
97   }
98
99   /* Move the sections themselves */
100   CIRCLEQ_FOREACH(ms, &me->scnList, elem) {
101     if (ms->shdr.sh_offset >= off) {
102       ms->shdr.sh_offset += size;
103     } else {
104       /* sh_offset < off */
105
106       /* If this was in a LOAD segment, it has been adjusted there
107          and this synchronises it.
108          If not, it doesn't matter anyway.
109        */
110       ms->shdr.sh_addr -= size;
111     }
112   }
113
114   return size;
115 }
116
117
118
119 /*
120  * Insert space at a given position in the file by moving everything
121  * after it towards the end of the file, and towards higher memory
122  * regions where it is mapped.
123  *
124  * off must not be in the middle of any data structure, such as
125  * PHDRs, SHDRs, or sections. Behaviour is undefined if it is.
126  *
127  * PHDRs will be patched such that everything AFTER off is shifted to
128  * higher addresses, making space for the new data in-between.
129  *
130  * CAUTION: This also moves NOBITS sections. If such are present,
131  *          use mExpandNobits() first and then inject at the end of
132  *          the expansion site.
133  */
134 GElf_Xword elfu_mInsertSpaceAfter(ElfuElf *me, GElf_Off off, GElf_Xword size)
135 {
136   ElfuScn *ms;
137   ElfuPhdr *mp;
138
139   assert(me);
140
141 /* Move SHDRs and PHDRs */
142   if (me->ehdr.e_shoff >= off) {
143     me->ehdr.e_shoff += size;
144   }
145
146   if (me->ehdr.e_phoff >= off) {
147     me->ehdr.e_phoff += size;
148   }
149
150   /* Patch PHDRs to include new data */
151   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
152     GElf_Off end = mp->phdr.p_offset + mp->phdr.p_filesz;
153
154     if (mp->phdr.p_offset >= off) {
155       /* Insertion before PHDR's content, so it's shifted. */
156       mp->phdr.p_offset += size;
157
158       /* It may also need to be remapped. See second pass below. */
159     } else {
160       /* mp->phdr.p_offset < off */
161
162       if (off < end) {
163         /* Insertion in the middle of PHDR, so let it span the new data */
164         mp->phdr.p_filesz += size;
165         mp->phdr.p_memsz += size;
166       } else {
167         /* Insertion after PHDR's content. Nothing to do. */
168       }
169     }
170   }
171
172   /* For each LOAD header, find clashing headers that need to be
173      remapped to higher memory areas.
174    */
175   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
176     if (mp->phdr.p_type == PT_LOAD) {
177       ElfuPhdr *mp2;
178
179       CIRCLEQ_FOREACH(mp2, &me->phdrList, elem) {
180         if (mp2->phdr.p_type != PT_LOAD
181             && mp2->phdr.p_offset + mp2->phdr.p_filesz > off) {
182           /* The PHDR now ends in the file after the injection site */
183           GElf_Off vend1 = mp->phdr.p_vaddr + mp->phdr.p_memsz;
184           GElf_Off pend1 = mp->phdr.p_paddr + mp->phdr.p_memsz;
185           GElf_Off vend2 = mp2->phdr.p_vaddr + mp2->phdr.p_memsz;
186           GElf_Off pend2 = mp2->phdr.p_paddr + mp2->phdr.p_memsz;
187
188           /* If mp and mp2 now overlap in memory */
189           if ((mp2->phdr.p_vaddr < vend1 && vend2 > mp->phdr.p_vaddr)
190               || (mp2->phdr.p_paddr < pend1 && pend2 > mp->phdr.p_paddr)) {
191             /* Move mp2 up in memory, as mp has been resized.
192                Maintaining the relative offset between them is the best
193                guess at maintaining consistency.
194              */
195
196             mp2->phdr.p_vaddr += size;
197             mp2->phdr.p_paddr += size;
198           }
199         }
200       }
201     }
202   }
203
204   /* Move the sections themselves */
205   CIRCLEQ_FOREACH(ms, &me->scnList, elem) {
206     if (ms->shdr.sh_offset >= off) {
207       ms->shdr.sh_offset += size;
208
209       /* If this was in a LOAD segment, it has been adjusted there
210          and this synchronises it.
211          If not, it doesn't matter anyway.
212        */
213       ms->shdr.sh_addr += size;
214     }
215   }
216
217   return size;
218 }
219
220
221
222
223
224 /* Update cross-references */
225 static void shiftSections(ElfuElf *me, ElfuScn *first)
226 {
227   ElfuScn *ms = first;
228   size_t firstIndex = elfu_mScnIndex(me, first);
229
230   do {
231     if (ms == me->shstrtab) {
232       me->ehdr.e_shstrndx++;
233     }
234
235     ms = CIRCLEQ_LOOP_NEXT(&me->scnList, ms, elem);
236   } while (ms != CIRCLEQ_FIRST(&me->scnList));
237
238   CIRCLEQ_FOREACH(ms, &me->scnList, elem) {
239     switch (ms->shdr.sh_type) {
240       case SHT_REL:
241       case SHT_RELA:
242         if (ms->shdr.sh_info >= firstIndex) {
243           ms->shdr.sh_info++;
244         }
245       case SHT_DYNAMIC:
246       case SHT_HASH:
247       case SHT_SYMTAB:
248       case SHT_DYNSYM:
249       case SHT_GNU_versym:
250       case SHT_GNU_verdef:
251       case SHT_GNU_verneed:
252         if (ms->shdr.sh_link >= firstIndex) {
253           ms->shdr.sh_link++;
254         }
255     }
256   }
257 }
258
259
260 /*
261  * Insert a section into an ELF model, /before/ a given other section
262  */
263 void elfu_mInsertScnInChainBefore(ElfuElf *me, ElfuScn *oldscn, ElfuScn *newscn)
264 {
265   assert(me);
266   assert(oldscn);
267   assert(newscn);
268
269   shiftSections(me, oldscn);
270
271   CIRCLEQ_INSERT_BEFORE(&me->scnList, oldscn, newscn, elem);
272 }
273
274
275 /*
276  * Insert a section into an ELF model, /after/ a given other section
277  */
278 void elfu_mInsertScnInChainAfter(ElfuElf *me, ElfuScn *oldscn, ElfuScn *newscn)
279 {
280   assert(me);
281   assert(oldscn);
282   assert(newscn);
283
284   if (oldscn != CIRCLEQ_LAST(&me->scnList)) {
285     shiftSections(me, CIRCLEQ_NEXT(oldscn, elem));
286   }
287
288   CIRCLEQ_INSERT_AFTER(&me->scnList, oldscn, newscn, elem);
289 }