Implement mInsertBefore, for pre-.interp injection
[centaur.git] / src / model / insert.c
1 #include <assert.h>
2 #include <sys/types.h>
3 #include <gelf.h>
4 #include <libelfu/libelfu.h>
5
6
7
8 /*
9  * Insert space at a given position in the file by moving everything
10  * after it towards the end of the file, and everything before it
11  * towards lower memory regions where it is mapped.
12  *
13  * off must not be in the middle of any data structure, such as
14  * PHDRs, SHDRs, or sections. Behaviour is undefined if it is.
15  *
16  * PHDRs will be patched such that everything AFTER off is mapped to
17  * the same address in memory, and everything BEFORE it is shifted to
18  * lower addresses, making space for the new data in-between.
19  */
20 GElf_Xword elfu_mInsertBefore(ElfuElf *me, GElf_Off off, GElf_Xword size)
21 {
22   ElfuScn *ms;
23   ElfuPhdr *mp;
24
25   assert(me);
26
27   // TODO: Take p_align into account
28
29   /* Move SHDRs and PHDRs */
30   if (me->ehdr.e_shoff >= off) {
31     me->ehdr.e_shoff += size;
32   }
33
34   if (me->ehdr.e_phoff >= off) {
35     me->ehdr.e_phoff += size;
36   }
37
38   /* Patch PHDRs to include new data */
39   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
40     GElf_Off end = mp->phdr.p_offset + mp->phdr.p_filesz;
41
42     if (mp->phdr.p_offset >= off) {
43       /* Insertion before PHDR's content, so it's just shifted */
44       mp->phdr.p_offset += size;
45     } else {
46       /* mp->phdr.p_offset < off */
47
48       if (off < end) {
49         /* Mark this as a modified area */
50
51         /* Insertion in the middle of PHDR, so let it span the new data */
52         mp->phdr.p_filesz += size;
53         mp->phdr.p_memsz += size;
54         mp->phdr.p_vaddr -= size;
55         mp->phdr.p_paddr -= size;
56       } else {
57         /* Insertion after PHDR's content, so it may need to be
58            remapped. This will happen in a second pass.
59          */
60       }
61     }
62   }
63
64   /* For each LOAD header, find clashing headers that need to be
65      remapped to lower memory areas.
66    */
67   CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
68     if (mp->phdr.p_type == PT_LOAD) {
69       ElfuPhdr *mp2;
70
71       CIRCLEQ_FOREACH(mp2, &me->phdrList, elem) {
72         if (mp2->phdr.p_type != PT_LOAD
73             && mp2->phdr.p_offset + mp2->phdr.p_filesz <= off) {
74           /* The PHDR ends in the file before the injection site */
75           GElf_Off vend1 = mp->phdr.p_vaddr + mp->phdr.p_memsz;
76           GElf_Off pend1 = mp->phdr.p_paddr + mp->phdr.p_memsz;
77           GElf_Off vend2 = mp2->phdr.p_vaddr + mp2->phdr.p_memsz;
78           GElf_Off pend2 = mp2->phdr.p_paddr + mp2->phdr.p_memsz;
79
80           /* If mp and mp2 now overlap in memory */
81           if ((mp2->phdr.p_vaddr < vend1 && vend2 > mp->phdr.p_vaddr)
82               || (mp2->phdr.p_paddr < pend1 && pend2 > mp->phdr.p_paddr)) {
83             /* Move mp2 down in memory, as mp has been resized.
84                Maintaining the relative offset between them is the best
85                guess at maintaining consistency.
86              */
87             mp2->phdr.p_vaddr -= size;
88             mp2->phdr.p_paddr -= size;
89           }
90         }
91       }
92     }
93   }
94
95   /* Move the sections themselves */
96   CIRCLEQ_FOREACH(ms, &me->scnList, elem) {
97     if (ms->shdr.sh_offset >= off) {
98       ms->shdr.sh_offset += size;
99     } else {
100       /* sh_offset < off */
101
102       /* If this was in a LOAD segment, it has been adjusted there
103          and this synchronises it.
104          If not, it doesn't matter anyway.
105        */
106       ms->shdr.sh_addr -= size;
107     }
108   }
109
110   return size;
111 }