about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJohn David Anglin <danglin@gcc.gnu.org>2017-07-16 10:51:13 -0400
committerJohn David Anglin <danglin@gcc.gnu.org>2017-07-16 10:51:13 -0400
commitdf3d455b23f3b901ecc1e4b7f1a600943f04471b (patch)
tree1b5981b9acafc67d85550d715d115f2b0d7950a8
parent66cd050f26c6e1130ff7998b7586c0003da23b46 (diff)
downloadglibc-df3d455b23f3b901ecc1e4b7f1a600943f04471b.tar.gz
glibc-df3d455b23f3b901ecc1e4b7f1a600943f04471b.tar.xz
glibc-df3d455b23f3b901ecc1e4b7f1a600943f04471b.zip
Fix [BZ 20098].
-rw-r--r--ChangeLog8
-rw-r--r--sysdeps/hppa/dl-fptr.c64
2 files changed, 54 insertions, 18 deletions
diff --git a/ChangeLog b/ChangeLog
index 2d3bfe26e0..c69c59b932 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2017-07-16  John David Anglin  <danglin@gcc.gnu.org>
+
+	[BZ 20098]
+	* sysdeps/hppa/dl-fptr.c (_dl_read_access_allowed): New.
+	(_dl_lookup_address): Return address if it is not consistent with
+	being a linker defined function pointer.  Likewise, return address
+	if address and function descriptor addresses are not accessible.
+
 2017-07-16  Siddhesh Poyarekar  <siddhesh@sourceware.org>
 
 	* po/libc.pot: Regenerate.
diff --git a/sysdeps/hppa/dl-fptr.c b/sysdeps/hppa/dl-fptr.c
index 83bdb91202..96fd09d192 100644
--- a/sysdeps/hppa/dl-fptr.c
+++ b/sysdeps/hppa/dl-fptr.c
@@ -28,6 +28,7 @@
 #include <dl-fptr.h>
 #include <dl-unmap-segments.h>
 #include <atomic.h>
+#include <libc-pointer-arith.h>
 
 #ifndef ELF_MACHINE_BOOT_FPTR_TABLE_LEN
 /* ELF_MACHINE_BOOT_FPTR_TABLE_LEN should be greater than the number of
@@ -181,24 +182,29 @@ make_fdesc (ElfW(Addr) ip, ElfW(Addr) gp)
 static inline ElfW(Addr) * __attribute__ ((always_inline))
 make_fptr_table (struct link_map *map)
 {
-  const ElfW(Sym) *symtab
-    = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
+  const ElfW(Sym) *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
   const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
   ElfW(Addr) *fptr_table;
   size_t size;
   size_t len;
+  const ElfW(Sym) *symtabend;
 
-  /* XXX Apparently the only way to find out the size of the dynamic
-     symbol section is to assume that the string table follows right
-     afterwards...  */
-  len = ((strtab - (char *) symtab)
+  /* Determine the end of the dynamic symbol table using the hash.  */
+  if (map->l_info[DT_HASH] != NULL)
+    symtabend = (symtab + ((Elf_Symndx *) D_PTR (map, l_info[DT_HASH]))[1]);
+  else
+  /* There is no direct way to determine the number of symbols in the
+     dynamic symbol table and no hash table is present.  The ELF
+     binary is ill-formed but what shall we do?  Use the beginning of
+     the string table which generally follows the symbol table.  */
+    symtabend = (const ElfW(Sym) *) strtab;
+
+  len = (((char *) symtabend - (char *) symtab)
 	 / map->l_info[DT_SYMENT]->d_un.d_val);
-  size = ((len * sizeof (fptr_table[0]) + GLRO(dl_pagesize) - 1)
-	  & -GLRO(dl_pagesize));
-  /* XXX We don't support here in the moment systems without MAP_ANON.
-     There probably are none for IA-64.  In case this is proven wrong
-     we will have to open /dev/null here and use the file descriptor
-     instead of the hard-coded -1.  */
+  size = ALIGN_UP (len * sizeof (fptr_table[0]), GLRO(dl_pagesize));
+
+  /* We don't support systems without MAP_ANON.  We avoid using malloc
+     because this might get called before malloc is setup.  */
   fptr_table = __mmap (NULL, size,
 		       PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,
 		       -1, 0);
@@ -331,22 +337,45 @@ elf_machine_resolve (void)
   return addr;
 }
 
+static inline int
+_dl_read_access_allowed (unsigned int *addr)
+{
+  int result;
+
+  asm ("proberi	(%1),3,%0" : "=r" (result) : "r" (addr) : );
+
+  return result;
+}
+
 ElfW(Addr)
 _dl_lookup_address (const void *address)
 {
   ElfW(Addr) addr = (ElfW(Addr)) address;
   unsigned int *desc, *gptr;
 
-  /* Check for special cases.  */
-  if ((int) addr == -1
-      || (unsigned int) addr < 4096
-      || !((unsigned int) addr & 2))
+  /* Return ADDR if the least-significant two bits of ADDR are not consistent
+     with ADDR being a linker defined function pointer.  The normal value for
+     a code address in a backtrace is 3.  */
+  if (((unsigned int) addr & 3) != 2)
+    return addr;
+
+  /* Handle special case where ADDR points to page 0.  */
+  if ((unsigned int) addr < 4096)
     return addr;
 
   /* Clear least-significant two bits from descriptor address.  */
   desc = (unsigned int *) ((unsigned int) addr & ~3);
+  if (!_dl_read_access_allowed (desc))
+    return addr;
 
-  /* Check if descriptor requires resolution.  The following trampoline is
+  /* Load first word of candidate descriptor.  It should be a pointer
+     with word alignment and point to memory that can be read.  */
+  gptr = (unsigned int *) desc[0];
+  if (((unsigned int) gptr & 3) != 0
+      || !_dl_read_access_allowed (gptr))
+    return addr;
+
+  /* See if descriptor requires resolution.  The following trampoline is
      used in each global offset table for function resolution:
 
 		ldw 0(r20),r22
@@ -358,7 +387,6 @@ _dl_lookup_address (const void *address)
 		.word "_dl_runtime_resolve ltp"
      got:	.word _DYNAMIC
 		.word "struct link map address" */
-  gptr = (unsigned int *) desc[0];
   if (gptr[0] == 0xea9f1fdd			/* b,l .-12,r20     */
       && gptr[1] == 0xd6801c1e			/* depwi 0,31,2,r20 */
       && (ElfW(Addr)) gptr[2] == elf_machine_resolve ())