summaryrefslogtreecommitdiff
path: root/src/libelfu/elfops/reorderphdrs.c
blob: cc1debc1edd13f5544afe88d12b500846ae99734 (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
112
113
114
115
116
117
118
119
/* This file is part of centaur.
 *
 * centaur is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License 2 as
 * published by the Free Software Foundation.

 * centaur is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with centaur.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <stdlib.h>
#include <libelfu/libelfu.h>


static int cmpPhdrType(const void *v1, const void *v2)
{
  const GElf_Phdr *p1 = (const GElf_Phdr*)v1;
  const GElf_Phdr *p2 = (const GElf_Phdr*)v2;

  /* These entries go first */
  if (p1->p_type == PT_PHDR) {
    return -1;
  } else if (p2->p_type == PT_PHDR) {
    return 1;
  } else if (p1->p_type == PT_INTERP) {
    return -1;
  } else if (p2->p_type == PT_INTERP) {
    return 1;
  }

  /* These entries go last */
  if (p1->p_type == PT_GNU_RELRO) {
    return 1;
  } else if (p2->p_type == PT_GNU_RELRO) {
    return -1;
  } else if (p1->p_type == PT_GNU_STACK) {
    return 1;
  } else if (p2->p_type == PT_GNU_STACK) {
    return -1;
  } else if (p1->p_type == PT_GNU_EH_FRAME) {
    return 1;
  } else if (p2->p_type == PT_GNU_EH_FRAME) {
    return -1;
  } else if (p1->p_type == PT_NOTE) {
    return 1;
  } else if (p2->p_type == PT_NOTE) {
    return -1;
  } else if (p1->p_type == PT_DYNAMIC) {
    return 1;
  } else if (p2->p_type == PT_DYNAMIC) {
    return -1;
  }

  /* Sort the rest by vaddr. */
  if (p1->p_vaddr < p2->p_vaddr) {
    return -1;
  } else if (p1->p_vaddr > p2->p_vaddr) {
    return 1;
  }

  /* Everything else is equal */
  return 0;
}



/* The ELF specification wants PHDR and INTERP headers to come first.
 * Typical GNU programs also contain DYNAMIC, NOTE, and GNU_* after
 * the LOAD segments.
 * Reorder them to comply with the spec. Otherwise the dynamic linker
 * will calculate the base address incorrectly and crash.
 *
 * Both Linux and glibc's ld.so are unhelpful here:
 * Linux calculates the PH table address as
 *     phdrs = (base + e_phoff)
 * and passes that to ld.so.
 * ld.so 'recovers' the base address as
 *     base = (phdrs - PT_PHDR.p_vaddr)
 *
 * Behold those who try to move the PHDR table away from the first
 * page of memory, where it is stored at the address calculated by
 * Linux. Things will crash. Badly.
 *
 * Unfortunately the ELF spec itself states that the PHT is in the
 * first page of memory. Not much we can do here.
 */
void elfu_eReorderPhdrs(Elf *e)
{
  size_t i, numPhdr;

  assert(e);

  assert(!elf_getphdrnum(e, &numPhdr));

  GElf_Phdr phdrs[numPhdr];

  for (i = 0; i < numPhdr; i++) {
    GElf_Phdr phdr;

    if (gelf_getphdr(e, i, &phdr) != &phdr) {
      ELFU_WARN("gelf_getphdr() failed for #%d: %s\n", i, elf_errmsg(-1));
      return;
    }

    phdrs[i] = phdr;
  }

  qsort(phdrs, numPhdr, sizeof(*phdrs), cmpPhdrType);

  for (i = 0; i < numPhdr; i++) {
    assert(gelf_update_phdr(e, i, &phdrs[i]));
  }
}