summaryrefslogtreecommitdiff
path: root/src/model/insert.c
blob: 2086c6e26659268ae0cbc2cb4ce65f743115d19c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <assert.h>
#include <sys/types.h>
#include <gelf.h>
#include <libelfu/libelfu.h>



/*
 * Insert space at a given position in the file by moving everything
 * after it towards the end of the file, and everything before it
 * towards lower memory regions where it is mapped.
 *
 * off must not be in the middle of any data structure, such as
 * PHDRs, SHDRs, or sections. Behaviour is undefined if it is.
 *
 * PHDRs will be patched such that everything AFTER off is mapped to
 * the same address in memory, and everything BEFORE it is shifted to
 * lower addresses, making space for the new data in-between.
 */
GElf_Xword elfu_mInsertBefore(ElfuElf *me, GElf_Off off, GElf_Xword size)
{
  ElfuScn *ms;
  ElfuPhdr *mp;

  assert(me);

  // TODO: Take p_align into account

  /* Move SHDRs and PHDRs */
  if (me->ehdr.e_shoff >= off) {
    me->ehdr.e_shoff += size;
  }

  if (me->ehdr.e_phoff >= off) {
    me->ehdr.e_phoff += size;
  }

  /* Patch PHDRs to include new data */
  CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
    GElf_Off end = mp->phdr.p_offset + mp->phdr.p_filesz;

    if (mp->phdr.p_offset >= off) {
      /* Insertion before PHDR's content, so it's just shifted */
      mp->phdr.p_offset += size;
    } else {
      /* mp->phdr.p_offset < off */

      if (off < end) {
        /* Mark this as a modified area */

        /* Insertion in the middle of PHDR, so let it span the new data */
        mp->phdr.p_filesz += size;
        mp->phdr.p_memsz += size;
        mp->phdr.p_vaddr -= size;
        mp->phdr.p_paddr -= size;
      } else {
        /* Insertion after PHDR's content, so it may need to be
           remapped. This will happen in a second pass.
         */
      }
    }
  }

  /* For each LOAD header, find clashing headers that need to be
     remapped to lower memory areas.
   */
  CIRCLEQ_FOREACH(mp, &me->phdrList, elem) {
    if (mp->phdr.p_type == PT_LOAD) {
      ElfuPhdr *mp2;

      CIRCLEQ_FOREACH(mp2, &me->phdrList, elem) {
        if (mp2->phdr.p_type != PT_LOAD
            && mp2->phdr.p_offset + mp2->phdr.p_filesz <= off) {
          /* The PHDR ends in the file before the injection site */
          GElf_Off vend1 = mp->phdr.p_vaddr + mp->phdr.p_memsz;
          GElf_Off pend1 = mp->phdr.p_paddr + mp->phdr.p_memsz;
          GElf_Off vend2 = mp2->phdr.p_vaddr + mp2->phdr.p_memsz;
          GElf_Off pend2 = mp2->phdr.p_paddr + mp2->phdr.p_memsz;

          /* If mp and mp2 now overlap in memory */
          if ((mp2->phdr.p_vaddr < vend1 && vend2 > mp->phdr.p_vaddr)
              || (mp2->phdr.p_paddr < pend1 && pend2 > mp->phdr.p_paddr)) {
            /* Move mp2 down in memory, as mp has been resized.
               Maintaining the relative offset between them is the best
               guess at maintaining consistency.
             */
            mp2->phdr.p_vaddr -= size;
            mp2->phdr.p_paddr -= size;
          }
        }
      }
    }
  }

  /* Move the sections themselves */
  CIRCLEQ_FOREACH(ms, &me->scnList, elem) {
    if (ms->shdr.sh_offset >= off) {
      ms->shdr.sh_offset += size;
    } else {
      /* sh_offset < off */

      /* If this was in a LOAD segment, it has been adjusted there
         and this synchronises it.
         If not, it doesn't matter anyway.
       */
      ms->shdr.sh_addr -= size;
    }
  }

  return size;
}