summaryrefslogtreecommitdiff
path: root/elf
diff options
context:
space:
mode:
authorCarlos O'Donell <carlos@redhat.com>2016-09-29 21:54:31 -0400
committerCarlos O'Donell <carlos@redhat.com>2016-09-30 01:36:56 -0400
commitd61ef7352b0026d9eeaf457dbfbb2e3fd8401e92 (patch)
tree798532132a35db0935de8cff3f10e8642e3457bb /elf
parent29cb9293326a27576965a40d50a898ee660dff81 (diff)
Bug 20292 - Simplify and test _dl_addr_inside_object
The function _dl_addr_inside_object is simplified by removing the conditional 'reladdr - l->l_phdr[n].p_vaddr >= 0' which is always true. The function is refactored into it's own object file and a unit test added to verify the correct behaviour of the function.
Diffstat (limited to 'elf')
-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
5 files changed, 309 insertions, 35 deletions
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"