summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog16
-rw-r--r--elf/Makefile12
-rw-r--r--elf/dl-addr-obj.c75
-rw-r--r--elf/dl-addr.c16
-rw-r--r--elf/dl-open.c18
-rw-r--r--elf/tst-_dl_addr_inside_object.c223
6 files changed, 325 insertions, 35 deletions
diff --git a/ChangeLog b/ChangeLog
index 44e8876bcb..86f4557abc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2016-09-30  Carlos O'Donell  <carlos@redhat.com>
+
+	[BZ #20292]
+	* elf/Makefile (routines): Add dl-addr-obj.
+	[ifeq (yesyes,$(have-fpie)$(build-shared))] (tests): Add
+	tst-_dl_addr_inside_object.
+	[ifeq (yesyes,$(have-fpie)$(build-shared))] (tests-pie): Likewise.
+	[ifeq (yesyes,$(have-fpie)$(build-shared))]
+	($(objpfx)tst-_dl_addr_inside_object): Add $(objpfx)dl-addr-obj.os.
+	[ifeq (yesyes,$(have-fpie)$(build-shared))]
+	(CFLAGS-tst-_dl_addr_inside_object.c): Add $(PIE-ccflag).
+	* elf/dl-addr.c: Remove _dl_addr_inside_object function.
+	* elf/dl-open.c: Likewise.
+	* elf/dl-addr-obj.c: New file.
+	* elf/tst-_dl_addr_inside_object.c: New file.
+
 2016-09-30  Joseph Myers  <joseph@codesourcery.com>
 
 	* math/math.h [__GLIBC_USE (IEC_60559_BFP_EXT)]: Include
diff --git a/elf/Makefile b/elf/Makefile
index 97f0ec248b..caffd92059 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -23,7 +23,7 @@ include ../Makeconfig
 
 headers		= elf.h bits/elfclass.h link.h bits/link.h
 routines	= $(all-dl-routines) dl-support dl-iteratephdr \
-		  dl-addr enbl-secure dl-profstub \
+		  dl-addr dl-addr-obj enbl-secure dl-profstub \
 		  dl-origin dl-libc dl-sym dl-tsd dl-sysdep
 
 # The core dynamic linking functions are in libc for the static and
@@ -319,6 +319,16 @@ tests-special += $(objpfx)tst-prelink-cmp.out
 endif
 endif
 
+# The test requires shared _and_ PIE because the executable
+# unit test driver must be able to link with the shared object
+# that is going to eventually go into an installed DSO.
+ifeq (yesyes,$(have-fpie)$(build-shared))
+tests += tst-_dl_addr_inside_object
+tests-pie += tst-_dl_addr_inside_object
+$(objpfx)tst-_dl_addr_inside_object: $(objpfx)dl-addr-obj.os
+CFLAGS-tst-_dl_addr_inside_object.c += $(PIE-ccflag)
+endif
+
 include ../Rules
 
 ifeq (yes,$(build-shared))
diff --git a/elf/dl-addr-obj.c b/elf/dl-addr-obj.c
new file mode 100644
index 0000000000..f6407610d1
--- /dev/null
+++ b/elf/dl-addr-obj.c
@@ -0,0 +1,75 @@
+/* Determine if address is inside object load segments.
+   Copyright (C) 1996-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <link.h>
+#include <elf.h>
+
+/* Return non-zero if ADDR lies within one of L's loadable segments.
+   We have three cases we care about.
+
+   Case 1: addr is above a segment.
+   +==================+<- l_map_end
+   |                  |<- addr
+   |------------------|<- l_addr + p_vaddr + p_memsz
+   |                  |
+   |                  |
+   |------------------|<- l_addr + p_vaddr
+   |------------------|<- l_addr
+   |                  |
+   +==================+<- l_map_start
+
+   Case 2: addr is within a segments.
+   +==================+<- l_map_end
+   |                  |
+   |------------------|<- l_addr + p_vaddr + p_memsz
+   |                  |<- addr
+   |                  |
+   |------------------|<- l_addr + p_vaddr
+   |------------------|<- l_addr
+   |                  |
+   +==================+<- l_map_start
+
+   Case 3: addr is below a segments.
+   +==================+<- l_map_end
+   |                  |
+   |------------------|<- l_addr + p_vaddr + p_memsz
+   |                  |
+   |                  |
+   |------------------|<- l_addr + p_vaddr
+   |------------------|<- l_addr
+   |                  |<- addr
+   +==================+<- l_map_start
+
+   All the arithmetic is unsigned and we shift all the values down by
+   l_addr + p_vaddr and then compare the normalized addr to the range
+   of interest i.e. 0 <= addr < p_memsz.
+
+*/
+int
+internal_function
+_dl_addr_inside_object (struct link_map *l, const ElfW(Addr) addr)
+{
+  int n = l->l_phnum;
+  const ElfW(Addr) reladdr = addr - l->l_addr;
+
+  while (--n >= 0)
+    if (l->l_phdr[n].p_type == PT_LOAD
+	&& reladdr - l->l_phdr[n].p_vaddr < l->l_phdr[n].p_memsz)
+      return 1;
+  return 0;
+}
diff --git a/elf/dl-addr.c b/elf/dl-addr.c
index 1b16a58ced..045e746680 100644
--- a/elf/dl-addr.c
+++ b/elf/dl-addr.c
@@ -144,19 +144,3 @@ _dl_addr (const void *address, Dl_info *info,
   return result;
 }
 libc_hidden_def (_dl_addr)
-
-/* Return non-zero if ADDR lies within one of L's segments.  */
-int
-internal_function
-_dl_addr_inside_object (struct link_map *l, const ElfW(Addr) addr)
-{
-  int n = l->l_phnum;
-  const ElfW(Addr) reladdr = addr - l->l_addr;
-
-  while (--n >= 0)
-    if (l->l_phdr[n].p_type == PT_LOAD
-	&& reladdr - l->l_phdr[n].p_vaddr >= 0
-	&& reladdr - l->l_phdr[n].p_vaddr < l->l_phdr[n].p_memsz)
-      return 1;
-  return 0;
-}
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 3e5df4891e..f5ca26180e 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -735,21 +735,3 @@ _dl_show_scope (struct link_map *l, int from)
     _dl_debug_printf (" no scope\n");
   _dl_debug_printf ("\n");
 }
-
-#if IS_IN (rtld)
-/* Return non-zero if ADDR lies within one of L's segments.  */
-int
-internal_function
-_dl_addr_inside_object (struct link_map *l, const ElfW(Addr) addr)
-{
-  int n = l->l_phnum;
-  const ElfW(Addr) reladdr = addr - l->l_addr;
-
-  while (--n >= 0)
-    if (l->l_phdr[n].p_type == PT_LOAD
-	&& reladdr - l->l_phdr[n].p_vaddr >= 0
-	&& reladdr - l->l_phdr[n].p_vaddr < l->l_phdr[n].p_memsz)
-      return 1;
-  return 0;
-}
-#endif
diff --git a/elf/tst-_dl_addr_inside_object.c b/elf/tst-_dl_addr_inside_object.c
new file mode 100644
index 0000000000..d1e45815c8
--- /dev/null
+++ b/elf/tst-_dl_addr_inside_object.c
@@ -0,0 +1,223 @@
+/* Unit test for _dl_addr_inside_object.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <link.h>
+#include <elf.h>
+#include <libc-symbols.h>
+
+extern int internal_function _dl_addr_inside_object (struct link_map *l,
+						     const ElfW(Addr) addr);
+
+static int
+do_test (void)
+{
+  int ret, err = 0;
+  ElfW(Addr) addr;
+  struct link_map map;
+  ElfW(Phdr) header;
+  map.l_phdr = &header;
+  map.l_phnum = 1;
+  map.l_addr = 0x0;
+  /* Segment spans 0x2000 -> 0x4000.  */
+  header.p_vaddr = 0x2000;
+  header.p_memsz = 0x2000;
+  header.p_type = PT_LOAD;
+  /* Address is above the segment e.g. > 0x4000.  */
+  addr = 0x5000;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("PASS: Above: Address is detected as outside the segment.\n");
+	break;
+      case 1:
+        printf ("FAIL: Above: Address is detected as inside the segment.\n");
+	err++;
+	break;
+      default:
+	printf ("FAIL: Above: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is inside the segment e.g. 0x2000 < addr < 0x4000.  */
+  addr = 0x3000;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("FAIL: Inside: Address is detected as outside the segment.\n");
+	err++;
+	break;
+      case 1:
+        printf ("PASS: Inside: Address is detected as inside the segment.\n");
+	break;
+      default:
+	printf ("FAIL: Inside: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is below the segment e.g. < 0x2000.  */
+  addr = 0x1000;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("PASS: Below: Address is detected as outside the segment.\n");
+	break;
+      case 1:
+        printf ("FAIL: Below: Address is detected as inside the segment.\n");
+	err++;
+	break;
+      default:
+	printf ("FAIL: Below: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is in the segment and addr == p_vaddr.  */
+  addr = 0x2000;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("FAIL: At p_vaddr: Address is detected as outside the segment.\n");
+	err++;
+	break;
+      case 1:
+        printf ("PASS: At p_vaddr: Address is detected as inside the segment.\n");
+	break;
+      default:
+	printf ("FAIL: At p_vaddr: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is in the segment and addr == p_vaddr + p_memsz - 1.  */
+  addr = 0x2000 + 0x2000 - 0x1;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("FAIL: At p_memsz-1: Address is detected as outside the segment.\n");
+	err++;
+	break;
+      case 1:
+        printf ("PASS: At p_memsz-1: Address is detected as inside the segment.\n");
+	break;
+      default:
+	printf ("FAIL: At p_memsz-1: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is outside the segment and addr == p_vaddr + p_memsz.  */
+  addr = 0x2000 + 0x2000;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("PASS: At p_memsz: Address is detected as outside the segment.\n");
+	break;
+      case 1:
+        printf ("FAIL: At p_memsz: Address is detected as inside the segment.\n");
+	err++;
+	break;
+      default:
+	printf ("FAIL: At p_memsz: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is outside the segment and p_vaddr at maximum address.  */
+  addr = 0x0 - 0x2;
+  header.p_vaddr = 0x0 - 0x1;
+  header.p_memsz = 0x1;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("PASS: At max: Address is detected as outside the segment.\n");
+	break;
+      case 1:
+        printf ("FAIL: At max: Address is detected as inside the segment.\n");
+	err++;
+	break;
+      default:
+	printf ("FAIL: At max: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is outside the segment and p_vaddr at minimum address.  */
+  addr = 0x1;
+  header.p_vaddr = 0x0;
+  header.p_memsz = 0x1;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("PASS: At min: Address is detected as outside the segment.\n");
+	break;
+      case 1:
+        printf ("FAIL: At min: Address is detected as inside the segment.\n");
+	err++;
+	break;
+      default:
+	printf ("FAIL: At min: Invalid return value.\n");
+	exit (1);
+    }
+  /* Address is always inside the segment with p_memsz at max.  */
+  addr = 0x0;
+  header.p_vaddr = 0x0;
+  header.p_memsz = 0x0 - 0x1;
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("FAIL: At maxmem: Address is detected as outside the segment.\n");
+	err++;
+	break;
+      case 1:
+        printf ("PASS: At maxmem: Address is detected as inside the segment.\n");
+	break;
+      default:
+	printf ("FAIL: At maxmem: Invalid return value.\n");
+	exit (1);
+    }
+  /* Attempt to wrap addr into the segment.
+     Pick a load address in the middle of the address space.
+     Place the test address at 0x0 so it wraps to the middle again.  */
+  map.l_addr = 0x0 - 0x1;
+  map.l_addr = map.l_addr / 2;
+  addr = 0;
+  /* Setup a segment covering 1/2 the address space.  */
+  header.p_vaddr = 0x0;
+  header.p_memsz = 0x0 - 0x1 - map.l_addr;
+  /* No matter where you place addr everything is shifted modulo l_addr
+     and even with this underflow you're always 1 byte away from being
+     in the range.  */
+  ret = _dl_addr_inside_object (&map, addr);
+  switch (ret)
+    {
+      case 0:
+	printf ("PASS: Underflow: Address is detected as outside the segment.\n");
+	break;
+      case 1:
+	printf ("FAIL: Underflow: Address is detected as inside the segment.\n");
+	err++;
+	break;
+      default:
+	printf ("FAIL: Underflow: Invalid return value.\n");
+	exit (1);
+    }
+
+  return err;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"