[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[committed] Verify file offsets of sections and section header table



Hi,

Function write_dso updates the section header table: it adds, removes and
updates sections.  It does this in two phases:
- it calculates the effect of changing sections on the following
  sections sh_offset (as well as on the section header table e_shoff)
- it makes sure sh_offset is sh_addralign aligned (as well as that the section
  header table e_shoff is ELFCLASS-appropriately aligned).

Add verification of the file offsets of the sections and the section header
table (treating the section header table as a pseudo section):
- verify that sections do not overlap before the first phase
- calculate the distance between sections before the first phase
- verify that the distances are the same after the first phase
- verify that sections do not overlap after the second phase
Do this in a fashion that is robust against unsorted section header
tables.

Committed to trunk.

Thanks,
- Tom

Verify file offsets of sections and section header table

2019-06-25  Tom de Vries  <tdevries@suse.de>

	* dwz.c (compare_section_numbers, sort_section_numbers, verify_sections)
	(calculate_section_distance): New function.
	(write_dso): Verify sections and section header table file offsets.

---
 dwz.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 198 insertions(+)

diff --git a/dwz.c b/dwz.c
index 101b6e3..3389ff0 100644
--- a/dwz.c
+++ b/dwz.c
@@ -10351,6 +10351,189 @@ error_out:
   return NULL;
 }
 
+/* Implicit arg for compare_section_numbers.  Could be passed in as explicit arg
+   when using qsort_r instead.  */
+static DSO *compare_section_numbers_implicit_arg;
+
+/* Helper functon for sort_section_numbers.  */
+static int
+compare_section_numbers (const void *p1, const void *p2)
+{
+  DSO *dso = compare_section_numbers_implicit_arg;
+  const int i1 = *(const int *)p1;
+  const int i2 = *(const int *)p2;
+  GElf_Off o1;
+  GElf_Off o2;
+
+  /* Keep element 0 at 0.  */
+  if (i1 == 0 || i2 == 0)
+    {
+      if (i1 == i2)
+	return 0;
+      if (i1 == 0)
+	return -1;
+      if (i2 == 0)
+	return 1;
+    }
+
+  /* Get file offsets.  */
+  o1 = (i1 == dso->ehdr.e_shnum
+	? dso->ehdr.e_shoff
+	: dso->shdr[i1].sh_offset);
+  o2 = (i2 == dso->ehdr.e_shnum
+	? dso->ehdr.e_shoff
+	: dso->shdr[i2].sh_offset);
+
+  /* Compare file offsets.  */
+  if (o1 < o2)
+    return -1;
+  if (o1 > o2)
+    return 1;
+
+  /* In case file offset is the same, keep the original relative order.  */
+  if (i1 < i2)
+    return -1;
+  if (i1 > i2)
+    return 1;
+
+  return 0;
+}
+
+/* Sort SORTED_SECTION_NUMBERS in file offset order.  */
+static void
+sort_section_numbers (DSO *dso, unsigned int *sorted_section_numbers)
+{
+  unsigned int i;
+  unsigned int nr_sections = dso->ehdr.e_shnum;
+
+  /* Treat section header table as another section, with index
+     dso->ehdr.e_shnum.  */
+  nr_sections += 1;
+
+  for (i = 0; i < nr_sections; ++i)
+    sorted_section_numbers[i] = i;
+
+  compare_section_numbers_implicit_arg = dso;
+  qsort (sorted_section_numbers, nr_sections,
+	 sizeof (sorted_section_numbers[0]), compare_section_numbers);
+  compare_section_numbers_implicit_arg = NULL;
+
+  assert (sorted_section_numbers[0] == 0);
+}
+
+/* Verify file offset and size of sections and section header table.  */
+static void
+verify_sections (DSO *dso, unsigned int *sorted_section_numbers,
+		 GElf_Off *distance, int addsec, GElf_Off addsize,
+		 GElf_Ehdr ehdr)
+{
+  int i, j;
+  int prev, update_prev;
+  GElf_Off offset, prev_offset, prev_size;
+  GElf_Off section_header_table_size
+    = dso->ehdr.e_shentsize * ehdr.e_shnum;
+
+  prev = -1;
+  for (i = 1, j = sorted_section_numbers[i];
+       i < (dso->ehdr.e_shnum + 1);
+       ++i, j = sorted_section_numbers[i], prev = update_prev)
+    {
+      if (j != dso->ehdr.e_shnum && dso->shdr[j].sh_type == SHT_NOBITS)
+	{
+	  update_prev = prev;
+	  continue;
+	}
+      update_prev = j;
+
+      if (prev == -1)
+	continue;
+
+      offset = (j == dso->ehdr.e_shnum
+		? ehdr.e_shoff
+		: dso->shdr[j].sh_offset);
+
+      prev_offset = (prev == dso->ehdr.e_shnum
+		     ? ehdr.e_shoff
+		     : dso->shdr[prev].sh_offset);
+
+      prev_size = (prev == dso->ehdr.e_shnum
+		   ? section_header_table_size
+		   : (dso->shdr[prev].sh_type == SHT_NOBITS
+		      ? 0
+		      : dso->shdr[prev].sh_size));
+
+      if (distance != NULL)
+	assert ((prev_offset + prev_size + distance[prev]
+		 + (prev == addsec ? addsize : 0))
+		== offset);
+      else
+	assert ((prev_offset + prev_size + (prev == addsec ? addsize : 0))
+		<= offset);
+    }
+}
+
+/* Calculate distance between sections and section header table.  */
+static int
+calculate_section_distance (DSO *dso, unsigned int *sorted_section_numbers,
+			    GElf_Off *distance)
+{
+  int i, j;
+  int prev, update_prev;
+  GElf_Off offset, prev_offset, prev_size;
+  GElf_Off section_header_table_size
+    = dso->ehdr.e_shentsize * dso->ehdr.e_shnum;
+
+  prev = -1;
+  for (i = 1, j = sorted_section_numbers[i];
+       i < (dso->ehdr.e_shnum + 1);
+       ++i, j = sorted_section_numbers[i], prev = update_prev)
+    {
+      if (j != dso->ehdr.e_shnum && dso->shdr[j].sh_type == SHT_NOBITS)
+	{
+	  update_prev = prev;
+	  continue;
+	}
+      update_prev = j;
+
+      if (prev == -1)
+	continue;
+
+      offset = (j == dso->ehdr.e_shnum
+		? dso->ehdr.e_shoff
+		: dso->shdr[j].sh_offset);
+
+      prev_offset = (prev == dso->ehdr.e_shnum
+		     ? dso->ehdr.e_shoff
+		     : dso->shdr[prev].sh_offset);
+
+      prev_size = (prev == dso->ehdr.e_shnum
+		   ? section_header_table_size
+		   : dso->shdr[prev].sh_size);
+
+      if (prev_offset + prev_size > offset)
+	{
+	  error (0, 0, "Section overlap detected");
+	  if (prev == dso->ehdr.e_shnum)
+	    error (0, 0, "Section header table: [0x%lx, 0x%ld)", prev_offset,
+		   prev_offset + prev_size);
+	  else
+	    error (0, 0, "Section %d: [0x%lx, 0x%ld)", j, prev_offset,
+		   prev_offset + prev_size);
+	  if (j == dso->ehdr.e_shnum)
+	    error (0, 0, "Section header table: 0x%lx", offset);
+	  else
+	    error (0, 0, "Section %d: 0x%lx", j, offset);
+	  return 1;
+	}
+
+      distance[prev] = offset - (prev_offset + prev_size);
+    }
+
+  verify_sections (dso, sorted_section_numbers, distance, -1, 0, dso->ehdr);
+
+  return 0;
+}
+
 /* Store new ELF into FILE.  debug_sections array contains
    new_data/new_size pairs where needed.  */
 static int
@@ -10366,10 +10549,17 @@ write_dso (DSO *dso, const char *file, struct stat *st)
   GElf_Word shstrtabadd = 0;
   char *shstrtab = NULL;
   bool remove_sections[SECTION_COUNT];
+  GElf_Off distance[dso->ehdr.e_shnum];
+  /* Array of sections and section header table sorted by file offset.  */
+  unsigned int sorted_section_numbers[dso->ehdr.e_shnum + 1];
 
   memset (remove_sections, '\0', sizeof (remove_sections));
   ehdr = dso->ehdr;
 
+  sort_section_numbers (dso, sorted_section_numbers);
+  if (calculate_section_distance (dso, sorted_section_numbers, distance))
+    return 1;
+
   for (i = 0; debug_sections[i].name; i++)
     if (debug_sections[i].new_size != debug_sections[i].size)
       {
@@ -10456,6 +10646,11 @@ write_dso (DSO *dso, const char *file, struct stat *st)
 	  }
       }
 
+  /* Verify that we did not change section layout, by checking that the
+     distances between sections and section header table remained the same.  */
+  verify_sections (dso, sorted_section_numbers, distance, addsec, addsize,
+		   ehdr);
+
   if (min_shoff != ~(GElf_Off) 0)
     {
       for (j = 1; j < dso->ehdr.e_shnum; ++j)
@@ -10523,6 +10718,9 @@ write_dso (DSO *dso, const char *file, struct stat *st)
 	}
     }
 
+  verify_sections (dso, sorted_section_numbers, NULL, addsec, addsize,
+		   ehdr);
+
   if (shstrtabadd != 0)
     {
       shstrtab = (char *) malloc (dso->shdr[dso->ehdr.e_shstrndx].sh_size);