458 lines
16 KiB
Diff
458 lines
16 KiB
Diff
commit 20681be149b9eb1b6c1f4246bf4bd801221c86cd
|
|
Author: Florian Weimer <fweimer@redhat.com>
|
|
Date: Fri Aug 1 10:20:23 2025 +0200
|
|
|
|
elf: Handle ld.so with LOAD segment gaps in _dl_find_object (bug 31943)
|
|
|
|
Detect if ld.so not contiguous and handle that case in _dl_find_object.
|
|
Set l_find_object_processed even for initially loaded link maps,
|
|
otherwise dlopen of an initially loaded object adds it to
|
|
_dlfo_loaded_mappings (where maps are expected to be contiguous),
|
|
in addition to _dlfo_nodelete_mappings.
|
|
|
|
Test elf/tst-link-map-contiguous-ldso iterates over the loader
|
|
image, reading every word to make sure memory is actually mapped.
|
|
It only does that if the l_contiguous flag is set for the link map.
|
|
Otherwise, it finds gaps with mmap and checks that _dl_find_object
|
|
does not return the ld.so mapping for them.
|
|
|
|
The test elf/tst-link-map-contiguous-main does the same thing for
|
|
the libc.so shared object. This only works if the kernel loaded
|
|
the main program because the glibc dynamic loader may fill
|
|
the gaps with PROT_NONE mappings in some cases, making it contiguous,
|
|
but accesses to individual words may still fault.
|
|
|
|
Test elf/tst-link-map-contiguous-libc is again slightly different
|
|
because the dynamic loader always fills the gaps with PROT_NONE
|
|
mappings, so a different form of probing has to be used.
|
|
|
|
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
|
|
|
Conflicts:
|
|
elf/dl-find_object.h
|
|
(missing SFrame support downstream. Update
|
|
l_find_object_processed early due to the possible
|
|
return from the for loop).
|
|
elf/dl-find_object.c
|
|
(missing is_rtld_link_map downstream)
|
|
elf/rtld.c
|
|
(missing GL(dl_rtld_map) refactoring downstream)
|
|
|
|
diff --git a/elf/Makefile b/elf/Makefile
|
|
index b37636c0f865f4e6..190ee83120c498a3 100644
|
|
--- a/elf/Makefile
|
|
+++ b/elf/Makefile
|
|
@@ -519,6 +519,8 @@ tests-internal += \
|
|
tst-dl-hwcaps_split \
|
|
tst-dlmopen2 \
|
|
tst-hash-collision3 \
|
|
+ tst-link-map-contiguous-ldso \
|
|
+ tst-link-map-contiguous-libc \
|
|
tst-ptrguard1 \
|
|
tst-stackguard1 \
|
|
tst-tls-surplus \
|
|
@@ -531,6 +533,10 @@ tests-internal += \
|
|
tst-dl_find_object tst-dl_find_object-threads \
|
|
# tests-internal
|
|
|
|
+ifeq ($(build-hardcoded-path-in-tests),yes)
|
|
+tests-internal += tst-link-map-contiguous-main
|
|
+endif
|
|
+
|
|
tests-container += \
|
|
tst-dlopen-self-container \
|
|
tst-dlopen-tlsmodid-container \
|
|
diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
|
|
index 62a8a61f6b6032cf..128329aa20ef9ed6 100644
|
|
--- a/elf/dl-find_object.c
|
|
+++ b/elf/dl-find_object.c
|
|
@@ -468,6 +468,37 @@ rtld_hidden_def (__dl_find_object_internal)
|
|
strong_alias (__dl_find_object_internal, _dl_find_object)
|
|
#endif
|
|
|
|
+/* Subroutine of _dlfo_process_initial to split out noncontigous link
|
|
+ maps. NODELETE is the number of used _dlfo_nodelete_mappings
|
|
+ elements. It is incremented as needed, and the new NODELETE value
|
|
+ is returned. */
|
|
+static size_t
|
|
+_dlfo_process_initial_noncontiguous_map (struct link_map *map,
|
|
+ size_t nodelete)
|
|
+{
|
|
+ struct dl_find_object_internal dlfo;
|
|
+ _dl_find_object_from_map (map, &dlfo);
|
|
+
|
|
+ /* PT_LOAD segments for a non-contiguous link map are added to the
|
|
+ non-closeable mappings. */
|
|
+ const ElfW(Phdr) *ph = map->l_phdr;
|
|
+ const ElfW(Phdr) *ph_end = map->l_phdr + map->l_phnum;
|
|
+ for (; ph < ph_end; ++ph)
|
|
+ if (ph->p_type == PT_LOAD)
|
|
+ {
|
|
+ if (_dlfo_nodelete_mappings != NULL)
|
|
+ {
|
|
+ /* Second pass only. */
|
|
+ _dlfo_nodelete_mappings[nodelete] = dlfo;
|
|
+ ElfW(Addr) start = ph->p_vaddr + map->l_addr;
|
|
+ _dlfo_nodelete_mappings[nodelete].map_start = start;
|
|
+ _dlfo_nodelete_mappings[nodelete].map_end = start + ph->p_memsz;
|
|
+ }
|
|
+ ++nodelete;
|
|
+ }
|
|
+ return nodelete;
|
|
+}
|
|
+
|
|
/* _dlfo_process_initial is called twice. First to compute the array
|
|
sizes from the initial loaded mappings. Second to fill in the
|
|
bases and infos arrays with the (still unsorted) data. Returns the
|
|
@@ -479,29 +510,8 @@ _dlfo_process_initial (void)
|
|
|
|
size_t nodelete = 0;
|
|
if (!main_map->l_contiguous)
|
|
- {
|
|
- struct dl_find_object_internal dlfo;
|
|
- _dl_find_object_from_map (main_map, &dlfo);
|
|
-
|
|
- /* PT_LOAD segments for a non-contiguous are added to the
|
|
- non-closeable mappings. */
|
|
- for (const ElfW(Phdr) *ph = main_map->l_phdr,
|
|
- *ph_end = main_map->l_phdr + main_map->l_phnum;
|
|
- ph < ph_end; ++ph)
|
|
- if (ph->p_type == PT_LOAD)
|
|
- {
|
|
- if (_dlfo_nodelete_mappings != NULL)
|
|
- {
|
|
- /* Second pass only. */
|
|
- _dlfo_nodelete_mappings[nodelete] = dlfo;
|
|
- _dlfo_nodelete_mappings[nodelete].map_start
|
|
- = ph->p_vaddr + main_map->l_addr;
|
|
- _dlfo_nodelete_mappings[nodelete].map_end
|
|
- = _dlfo_nodelete_mappings[nodelete].map_start + ph->p_memsz;
|
|
- }
|
|
- ++nodelete;
|
|
- }
|
|
- }
|
|
+ /* Contiguous case already handled in _dl_find_object_init. */
|
|
+ nodelete = _dlfo_process_initial_noncontiguous_map (main_map, nodelete);
|
|
|
|
size_t loaded = 0;
|
|
for (Lmid_t ns = 0; ns < GL(dl_nns); ++ns)
|
|
@@ -513,11 +523,22 @@ _dlfo_process_initial (void)
|
|
/* lt_library link maps are implicitly NODELETE. */
|
|
if (l->l_type == lt_library || l->l_nodelete_active)
|
|
{
|
|
- if (_dlfo_nodelete_mappings != NULL)
|
|
- /* Second pass only. */
|
|
- _dl_find_object_from_map
|
|
- (l, _dlfo_nodelete_mappings + nodelete);
|
|
- ++nodelete;
|
|
+ /* The kernel may have loaded ld.so with gaps. */
|
|
+ if (!l->l_contiguous
|
|
+#ifdef SHARED
|
|
+ && l == &GL(dl_rtld_map)
|
|
+#endif
|
|
+ )
|
|
+ nodelete
|
|
+ = _dlfo_process_initial_noncontiguous_map (l, nodelete);
|
|
+ else
|
|
+ {
|
|
+ if (_dlfo_nodelete_mappings != NULL)
|
|
+ /* Second pass only. */
|
|
+ _dl_find_object_from_map
|
|
+ (l, _dlfo_nodelete_mappings + nodelete);
|
|
+ ++nodelete;
|
|
+ }
|
|
}
|
|
else if (l->l_type == lt_loaded)
|
|
{
|
|
@@ -767,7 +788,6 @@ _dl_find_object_update_1 (struct link_map **loaded, size_t count)
|
|
/* Prefer newly loaded link map. */
|
|
assert (loaded_index1 > 0);
|
|
_dl_find_object_from_map (loaded[loaded_index1 - 1], dlfo);
|
|
- loaded[loaded_index1 - 1]->l_find_object_processed = 1;
|
|
--loaded_index1;
|
|
}
|
|
|
|
diff --git a/elf/dl-find_object.h b/elf/dl-find_object.h
|
|
index 11569efc9b7daf9c..fae25747edc6dca0 100644
|
|
--- a/elf/dl-find_object.h
|
|
+++ b/elf/dl-find_object.h
|
|
@@ -87,7 +87,7 @@ _dl_find_object_to_external (struct dl_find_object_internal *internal,
|
|
}
|
|
|
|
/* Extract the object location data from a link map and writes it to
|
|
- *RESULT using relaxed MO stores. */
|
|
+ *RESULT using relaxed MO stores. Set L->l_find_object_processed. */
|
|
static void __attribute__ ((unused))
|
|
_dl_find_object_from_map (struct link_map *l,
|
|
struct dl_find_object_internal *result)
|
|
@@ -100,6 +100,8 @@ _dl_find_object_from_map (struct link_map *l,
|
|
atomic_store_relaxed (&result->eh_dbase, (void *) l->l_info[DT_PLTGOT]);
|
|
#endif
|
|
|
|
+ l->l_find_object_processed = 1;
|
|
+
|
|
for (const ElfW(Phdr) *ph = l->l_phdr, *ph_end = l->l_phdr + l->l_phnum;
|
|
ph < ph_end; ++ph)
|
|
if (ph->p_type == DLFO_EH_SEGMENT_TYPE)
|
|
diff --git a/elf/rtld.c b/elf/rtld.c
|
|
index a9073d4e14b07410..425003e6c8e452ab 100644
|
|
--- a/elf/rtld.c
|
|
+++ b/elf/rtld.c
|
|
@@ -1308,7 +1308,7 @@ rtld_setup_main_map (struct link_map *main_map)
|
|
|
|
/* Set up the program header information for the dynamic linker
|
|
itself. It can be accessed via _r_debug and dl_iterate_phdr
|
|
- callbacks. */
|
|
+ callbacks, and it is used by _dl_find_object. */
|
|
static void
|
|
rtld_setup_phdr (void)
|
|
{
|
|
@@ -1334,6 +1334,29 @@ rtld_setup_phdr (void)
|
|
GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
|
|
|
|
|
|
+ GL(dl_rtld_map).l_contiguous = 1;
|
|
+ /* The linker may not have produced a contiguous object. The kernel
|
|
+ will load the object with actual gaps (unlike the glibc loader
|
|
+ for shared objects, which always produces a contiguous mapping).
|
|
+ See similar logic in rtld_setup_main_map above. */
|
|
+ {
|
|
+ ElfW(Addr) expected_load_address = 0;
|
|
+ for (const ElfW(Phdr) *ph = rtld_phdr; ph < &rtld_phdr[rtld_ehdr->e_phnum];
|
|
+ ++ph)
|
|
+ if (ph->p_type == PT_LOAD)
|
|
+ {
|
|
+ ElfW(Addr) mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
|
|
+ if (GL(dl_rtld_map).l_contiguous && expected_load_address != 0
|
|
+ && expected_load_address != mapstart)
|
|
+ GL(dl_rtld_map).l_contiguous = 0;
|
|
+ ElfW(Addr) allocend = ph->p_vaddr + ph->p_memsz;
|
|
+ /* The next expected address is the page following this load
|
|
+ segment. */
|
|
+ expected_load_address = ((allocend + GLRO(dl_pagesize) - 1)
|
|
+ & ~(GLRO(dl_pagesize) - 1));
|
|
+ }
|
|
+ }
|
|
+
|
|
/* PT_GNU_RELRO is usually the last phdr. */
|
|
size_t cnt = rtld_ehdr->e_phnum;
|
|
while (cnt-- > 0)
|
|
diff --git a/elf/tst-link-map-contiguous-ldso.c b/elf/tst-link-map-contiguous-ldso.c
|
|
new file mode 100644
|
|
index 0000000000000000..04de808bb234fe38
|
|
--- /dev/null
|
|
+++ b/elf/tst-link-map-contiguous-ldso.c
|
|
@@ -0,0 +1,98 @@
|
|
+/* Check that _dl_find_object behavior matches up with gaps.
|
|
+ Copyright (C) 2025 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
|
|
+ <https://www.gnu.org/licenses/>. */
|
|
+
|
|
+#include <dlfcn.h>
|
|
+#include <gnu/lib-names.h>
|
|
+#include <link.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdio.h>
|
|
+#include <support/check.h>
|
|
+#include <support/xdlfcn.h>
|
|
+#include <support/xunistd.h>
|
|
+#include <sys/mman.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+static int
|
|
+do_test (void)
|
|
+{
|
|
+ struct link_map *l = xdlopen (LD_SO, RTLD_NOW);
|
|
+ if (!l->l_contiguous)
|
|
+ {
|
|
+ puts ("info: ld.so link map is not contiguous");
|
|
+
|
|
+ /* Try to find holes by probing with mmap. */
|
|
+ int pagesize = getpagesize ();
|
|
+ bool gap_found = false;
|
|
+ ElfW(Addr) addr = l->l_map_start;
|
|
+ TEST_COMPARE (addr % pagesize, 0);
|
|
+ while (addr < l->l_map_end)
|
|
+ {
|
|
+ void *expected = (void *) addr;
|
|
+ void *ptr = xmmap (expected, 1, PROT_READ | PROT_WRITE,
|
|
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
|
|
+ struct dl_find_object dlfo;
|
|
+ int dlfo_ret = _dl_find_object (expected, &dlfo);
|
|
+ if (ptr == expected)
|
|
+ {
|
|
+ if (dlfo_ret < 0)
|
|
+ {
|
|
+ TEST_COMPARE (dlfo_ret, -1);
|
|
+ printf ("info: hole without mapping data found at %p\n", ptr);
|
|
+ }
|
|
+ else
|
|
+ FAIL ("object \"%s\" found in gap at %p",
|
|
+ dlfo.dlfo_link_map->l_name, ptr);
|
|
+ gap_found = true;
|
|
+ }
|
|
+ else if (dlfo_ret == 0)
|
|
+ {
|
|
+ if ((void *) dlfo.dlfo_link_map != (void *) l)
|
|
+ {
|
|
+ printf ("info: object \"%s\" found at %p\n",
|
|
+ dlfo.dlfo_link_map->l_name, ptr);
|
|
+ gap_found = true;
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ TEST_COMPARE (dlfo_ret, -1);
|
|
+ xmunmap (ptr, 1);
|
|
+ addr += pagesize;
|
|
+ }
|
|
+ if (!gap_found)
|
|
+ FAIL ("no ld.so gap found");
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ puts ("info: ld.so link map is contiguous");
|
|
+
|
|
+ /* Assert that ld.so is truly contiguous in memory. */
|
|
+ volatile long int *p = (volatile long int *) l->l_map_start;
|
|
+ volatile long int *end = (volatile long int *) l->l_map_end;
|
|
+ while (p < end)
|
|
+ {
|
|
+ *p;
|
|
+ ++p;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ xdlclose (l);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#include <support/test-driver.c>
|
|
diff --git a/elf/tst-link-map-contiguous-libc.c b/elf/tst-link-map-contiguous-libc.c
|
|
new file mode 100644
|
|
index 0000000000000000..eb5728c765ac3cfb
|
|
--- /dev/null
|
|
+++ b/elf/tst-link-map-contiguous-libc.c
|
|
@@ -0,0 +1,57 @@
|
|
+/* Check that the entire libc.so program image is readable if contiguous.
|
|
+ Copyright (C) 2025 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
|
|
+ <https://www.gnu.org/licenses/>. */
|
|
+
|
|
+#include <gnu/lib-names.h>
|
|
+#include <link.h>
|
|
+#include <support/check.h>
|
|
+#include <support/xdlfcn.h>
|
|
+#include <support/xunistd.h>
|
|
+#include <sys/mman.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+static int
|
|
+do_test (void)
|
|
+{
|
|
+ struct link_map *l = xdlopen (LIBC_SO, RTLD_NOW);
|
|
+
|
|
+ /* The dynamic loader fills holes with PROT_NONE mappings. */
|
|
+ if (!l->l_contiguous)
|
|
+ FAIL_EXIT1 ("libc.so link map is not contiguous");
|
|
+
|
|
+ /* Direct probing does not work because not everything is readable
|
|
+ due to PROT_NONE mappings. */
|
|
+ int pagesize = getpagesize ();
|
|
+ ElfW(Addr) addr = l->l_map_start;
|
|
+ TEST_COMPARE (addr % pagesize, 0);
|
|
+ while (addr < l->l_map_end)
|
|
+ {
|
|
+ void *expected = (void *) addr;
|
|
+ void *ptr = xmmap (expected, 1, PROT_READ | PROT_WRITE,
|
|
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
|
|
+ if (ptr == expected)
|
|
+ FAIL ("hole in libc.so memory image after %lu bytes",
|
|
+ (unsigned long int) (addr - l->l_map_start));
|
|
+ xmunmap (ptr, 1);
|
|
+ addr += pagesize;
|
|
+ }
|
|
+
|
|
+ xdlclose (l);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#include <support/test-driver.c>
|
|
diff --git a/elf/tst-link-map-contiguous-main.c b/elf/tst-link-map-contiguous-main.c
|
|
new file mode 100644
|
|
index 0000000000000000..2d1a054f0fbb0855
|
|
--- /dev/null
|
|
+++ b/elf/tst-link-map-contiguous-main.c
|
|
@@ -0,0 +1,45 @@
|
|
+/* Check that the entire main program image is readable if contiguous.
|
|
+ Copyright (C) 2025 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
|
|
+ <https://www.gnu.org/licenses/>. */
|
|
+
|
|
+#include <link.h>
|
|
+#include <support/check.h>
|
|
+#include <support/xdlfcn.h>
|
|
+
|
|
+static int
|
|
+do_test (void)
|
|
+{
|
|
+ struct link_map *l = xdlopen ("", RTLD_NOW);
|
|
+ if (!l->l_contiguous)
|
|
+ FAIL_UNSUPPORTED ("main link map is not contiguous");
|
|
+
|
|
+ /* This check only works if the kernel loaded the main program. The
|
|
+ dynamic loader replaces gaps with PROT_NONE mappings, resulting
|
|
+ in faults. */
|
|
+ volatile long int *p = (volatile long int *) l->l_map_start;
|
|
+ volatile long int *end = (volatile long int *) l->l_map_end;
|
|
+ while (p < end)
|
|
+ {
|
|
+ *p;
|
|
+ ++p;
|
|
+ }
|
|
+
|
|
+ xdlclose (l);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#include <support/test-driver.c>
|