Test for overflow in eh_frame_hdr entries and for overlapping FDEs
authorAlan Modra <amodra@gmail.com>
Fri, 12 Sep 2014 00:05:42 +0000 (09:35 +0930)
committerAlan Modra <amodra@gmail.com>
Fri, 12 Sep 2014 00:14:09 +0000 (09:44 +0930)
With larger binaries on 64-bit systems, or indeed just binaries that
have a large gap between text and data, it is possible for the
.eh_frame_hdr lookup table entry values to overflow a signed 32-bit
relative offset.  It is also a requirement for the glibc FDE lookup
code that only one FDE claim to cover any given address.

* elf-bfd.h (struct eh_frame_array_ent): Add "range".
* elf-eh-frame.c (_bfd_elf_write_section_eh_frame): Stash address
range of FDEs to hdr_info->array.
(_bfd_elf_write_section_eh_frame_hdr): Report overflow in
.eh_frame_hdr entries, and overlapping FDEs.

bfd/ChangeLog
bfd/elf-bfd.h
bfd/elf-eh-frame.c

index 882d9947d075c54e2eaf44cc0ba4816da68709c2..0bdebfc7a04f375bc5e044af1e31f7f0f5e52363 100644 (file)
@@ -1,3 +1,11 @@
+2014-09-12  Alan Modra  <amodra@gmail.com>
+
+       * elf-bfd.h (struct eh_frame_array_ent): Add "range".
+       * elf-eh-frame.c (_bfd_elf_write_section_eh_frame): Stash address
+       range of FDEs to hdr_info->array.
+       (_bfd_elf_write_section_eh_frame_hdr): Report overflow in
+       .eh_frame_hdr entries, and overlapping FDEs.
+
 2014-09-10  Alan Modra  <amodra@gmail.com>
 
        * elf.c (assign_file_positions_except_relocs): Move section header
index 2f24274d3f1fb64a3c753b4c1bc180381a8ee1bd..dc343ece475d11e1256cf8d7ce37254bd30f9c8a 100644 (file)
@@ -372,6 +372,7 @@ struct eh_frame_sec_info
 struct eh_frame_array_ent
 {
   bfd_vma initial_loc;
+  bfd_size_type range;
   bfd_vma fde;
 };
 
index d56440c334f3d595163d1f9d8ddf50580037ccff..02f2d2322ae77b028f41500d31d838b5ddafd396 100644 (file)
@@ -1632,6 +1632,8 @@ _bfd_elf_write_section_eh_frame (bfd *abfd,
              if (sizeof (address) > 4 && ptr_size == 4)
                address &= 0xffffffff;
              hdr_info->array[hdr_info->array_count].initial_loc = address;
+             hdr_info->array[hdr_info->array_count].range
+               = read_value (abfd, buf + width, width, FALSE);
              hdr_info->array[hdr_info->array_count++].fde
                = (sec->output_section->vma
                   + sec->output_offset
@@ -1805,26 +1807,55 @@ _bfd_elf_write_section_eh_frame_hdr (bfd *abfd, struct bfd_link_info *info)
       if (contents[2] != DW_EH_PE_omit)
        {
          unsigned int i;
+         bfd_boolean overlap, overflow;
 
          bfd_put_32 (abfd, hdr_info->fde_count, contents + EH_FRAME_HDR_SIZE);
          qsort (hdr_info->array, hdr_info->fde_count,
                 sizeof (*hdr_info->array), vma_compare);
+         overlap = FALSE;
+         overflow = FALSE;
          for (i = 0; i < hdr_info->fde_count; i++)
            {
-             bfd_put_32 (abfd,
-                         hdr_info->array[i].initial_loc
-                         - sec->output_section->vma,
-                         contents + EH_FRAME_HDR_SIZE + i * 8 + 4);
-             bfd_put_32 (abfd,
-                         hdr_info->array[i].fde - sec->output_section->vma,
-                         contents + EH_FRAME_HDR_SIZE + i * 8 + 8);
+             bfd_vma val;
+
+             val = hdr_info->array[i].initial_loc - sec->output_section->vma;
+             val = ((val & 0xffffffff) ^ 0x80000000) - 0x80000000;
+             if (elf_elfheader (abfd)->e_ident[EI_CLASS] == ELFCLASS64
+                 && (hdr_info->array[i].initial_loc
+                     != sec->output_section->vma + val))
+               overflow = TRUE;
+             bfd_put_32 (abfd, val, contents + EH_FRAME_HDR_SIZE + i * 8 + 4);
+             val = hdr_info->array[i].fde - sec->output_section->vma;
+             val = ((val & 0xffffffff) ^ 0x80000000) - 0x80000000;
+             if (elf_elfheader (abfd)->e_ident[EI_CLASS] == ELFCLASS64
+                 && (hdr_info->array[i].fde
+                     != sec->output_section->vma + val))
+               overflow = TRUE;
+             bfd_put_32 (abfd, val, contents + EH_FRAME_HDR_SIZE + i * 8 + 8);
+             if (i != 0
+                 && (hdr_info->array[i].initial_loc
+                     < (hdr_info->array[i - 1].initial_loc
+                        + hdr_info->array[i - 1].range)))
+               overlap = TRUE;
+           }
+         if (overflow)
+           (*info->callbacks->einfo)
+             (_("%P: .eh_frame_hdr entry overflow.\n"));
+         if (overlap)
+           (*info->callbacks->einfo)
+             (_("%P: .eh_frame_hdr refers to overlapping FDEs.\n"));
+         if (overflow || overlap)
+           {
+             bfd_set_error (bfd_error_bad_value);
+             retval = FALSE;
            }
        }
 
       /* FIXME: octets_per_byte.  */
-      retval = bfd_set_section_contents (abfd, sec->output_section, contents,
-                                        (file_ptr) sec->output_offset,
-                                        sec->size);
+      if (!bfd_set_section_contents (abfd, sec->output_section, contents,
+                                    (file_ptr) sec->output_offset,
+                                    sec->size))
+       retval = FALSE;
       free (contents);
     }
   if (hdr_info->array != NULL)