Reorder PHDRs according to ELF spec
authornorly <ny-git@enpas.org>
Mon, 24 Jun 2013 02:34:19 +0000 (03:34 +0100)
committernorly <ny-git@enpas.org>
Mon, 24 Jun 2013 02:59:02 +0000 (03:59 +0100)
include/libelfu/elfops.h
src/libelfu/elfops/reorderphdrs.c [new file with mode: 0644]
src/libelfu/modelops/toFile.c
tests/reference/putsmain32-cloned
tests/reference/putsmain32-with-puts-alternative32

index 48ef3808641df972673242418532f9ed8a7528c1..4131f410967a96e82fb449b03171cbf81205ddc9 100644 (file)
@@ -8,6 +8,7 @@
 
 
 int elfu_eCheck(Elf *e);
+void elfu_eReorderPhdrs(Elf *e);
 
 
 #endif
diff --git a/src/libelfu/elfops/reorderphdrs.c b/src/libelfu/elfops/reorderphdrs.c
new file mode 100644 (file)
index 0000000..c2c92fd
--- /dev/null
@@ -0,0 +1,104 @@
+#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]));
+  }
+}
index b42bc08076cfa6bf1dea720eb58d83ad8236bb3e..7b9e1f4d598dfad660369ac31e52fdd6890667e1 100644 (file)
@@ -110,6 +110,8 @@ void elfu_mToElf(ElfuElf *me, Elf *e)
 
   elfu_mPhdrForall(me, modelToPhdr, &i, e);
 
+  elfu_eReorderPhdrs(e);
+
 
   /* Done */
   elf_flagelf(e, ELF_C_SET, ELF_F_DIRTY);
index 973ea9c01bdb771776c0a2dfcddf8cc804f79e49..58328b4f0cc4bbe596d38b57e00fbf0e4d754c32 100755 (executable)
Binary files a/tests/reference/putsmain32-cloned and b/tests/reference/putsmain32-cloned differ
index c820ef55851e56e8e9e9dc17e7cc2fcbed99103d..953ddad817253b60089f9767dd21e825a416099d 100755 (executable)
Binary files a/tests/reference/putsmain32-with-puts-alternative32 and b/tests/reference/putsmain32-with-puts-alternative32 differ