559 lines
20 KiB
Diff
559 lines
20 KiB
Diff
|
|
From 2fadee530155bae6682ab2965d6ff3a2fc9eced6 Mon Sep 17 00:00:00 2001
|
||
|
|
From: Florian Weimer <fweimer@redhat.com>
|
||
|
|
Date: Fri, 1 Aug 2025 19:27:04 +0200
|
||
|
|
Subject: [PATCH] elf: Extract rtld_setup_phdr function from dl_main
|
||
|
|
|
||
|
|
Remove historic binutils reference from comment and update
|
||
|
|
how this data is used by applications.
|
||
|
|
|
||
|
|
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||
|
|
(cherry picked from commit 2cac9559e06044ba520e785c151fbbd25011865f)
|
||
|
|
---
|
||
|
|
elf/rtld.c | 59 +++++++++++++++++++++++++++++-------------------------
|
||
|
|
1 file changed, 32 insertions(+), 27 deletions(-)
|
||
|
|
|
||
|
|
diff --git a/elf/rtld.c b/elf/rtld.c
|
||
|
|
index 493f9696ea..6fb900fb31 100644
|
||
|
|
--- a/elf/rtld.c
|
||
|
|
+++ b/elf/rtld.c
|
||
|
|
@@ -1239,6 +1239,37 @@ rtld_setup_main_map (struct link_map *main_map)
|
||
|
|
return has_interp;
|
||
|
|
}
|
||
|
|
|
||
|
|
+/* Set up the program header information for the dynamic linker
|
||
|
|
+ itself. It can be accessed via _r_debug and dl_iterate_phdr
|
||
|
|
+ callbacks. */
|
||
|
|
+static void
|
||
|
|
+rtld_setup_phdr (void)
|
||
|
|
+{
|
||
|
|
+ /* Starting from binutils-2.23, the linker will define the magic
|
||
|
|
+ symbol __ehdr_start to point to our own ELF header if it is
|
||
|
|
+ visible in a segment that also includes the phdrs. */
|
||
|
|
+
|
||
|
|
+ const ElfW(Ehdr) *rtld_ehdr = &__ehdr_start;
|
||
|
|
+ assert (rtld_ehdr->e_ehsize == sizeof *rtld_ehdr);
|
||
|
|
+ assert (rtld_ehdr->e_phentsize == sizeof (ElfW(Phdr)));
|
||
|
|
+
|
||
|
|
+ const ElfW(Phdr) *rtld_phdr = (const void *) rtld_ehdr + rtld_ehdr->e_phoff;
|
||
|
|
+
|
||
|
|
+ _dl_rtld_map.l_phdr = rtld_phdr;
|
||
|
|
+ _dl_rtld_map.l_phnum = rtld_ehdr->e_phnum;
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+ /* PT_GNU_RELRO is usually the last phdr. */
|
||
|
|
+ size_t cnt = rtld_ehdr->e_phnum;
|
||
|
|
+ while (cnt-- > 0)
|
||
|
|
+ if (rtld_phdr[cnt].p_type == PT_GNU_RELRO)
|
||
|
|
+ {
|
||
|
|
+ _dl_rtld_map.l_relro_addr = rtld_phdr[cnt].p_vaddr;
|
||
|
|
+ _dl_rtld_map.l_relro_size = rtld_phdr[cnt].p_memsz;
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
/* Adjusts the contents of the stack and related globals for the user
|
||
|
|
entry point. The ld.so processed skip_args arguments and bumped
|
||
|
|
_dl_argv and _dl_argc accordingly. Those arguments are removed from
|
||
|
|
@@ -1705,33 +1736,7 @@ dl_main (const ElfW(Phdr) *phdr,
|
||
|
|
++GL(dl_ns)[LM_ID_BASE]._ns_nloaded;
|
||
|
|
++GL(dl_load_adds);
|
||
|
|
|
||
|
|
- /* Starting from binutils-2.23, the linker will define the magic symbol
|
||
|
|
- __ehdr_start to point to our own ELF header if it is visible in a
|
||
|
|
- segment that also includes the phdrs. If that's not available, we use
|
||
|
|
- the old method that assumes the beginning of the file is part of the
|
||
|
|
- lowest-addressed PT_LOAD segment. */
|
||
|
|
-
|
||
|
|
- /* Set up the program header information for the dynamic linker
|
||
|
|
- itself. It is needed in the dl_iterate_phdr callbacks. */
|
||
|
|
- const ElfW(Ehdr) *rtld_ehdr = &__ehdr_start;
|
||
|
|
- assert (rtld_ehdr->e_ehsize == sizeof *rtld_ehdr);
|
||
|
|
- assert (rtld_ehdr->e_phentsize == sizeof (ElfW(Phdr)));
|
||
|
|
-
|
||
|
|
- const ElfW(Phdr) *rtld_phdr = (const void *) rtld_ehdr + rtld_ehdr->e_phoff;
|
||
|
|
-
|
||
|
|
- _dl_rtld_map.l_phdr = rtld_phdr;
|
||
|
|
- _dl_rtld_map.l_phnum = rtld_ehdr->e_phnum;
|
||
|
|
-
|
||
|
|
-
|
||
|
|
- /* PT_GNU_RELRO is usually the last phdr. */
|
||
|
|
- size_t cnt = rtld_ehdr->e_phnum;
|
||
|
|
- while (cnt-- > 0)
|
||
|
|
- if (rtld_phdr[cnt].p_type == PT_GNU_RELRO)
|
||
|
|
- {
|
||
|
|
- _dl_rtld_map.l_relro_addr = rtld_phdr[cnt].p_vaddr;
|
||
|
|
- _dl_rtld_map.l_relro_size = rtld_phdr[cnt].p_memsz;
|
||
|
|
- break;
|
||
|
|
- }
|
||
|
|
+ rtld_setup_phdr ();
|
||
|
|
|
||
|
|
/* Add the dynamic linker to the TLS list if it also uses TLS. */
|
||
|
|
if (_dl_rtld_map.l_tls_blocksize != 0)
|
||
|
|
--
|
||
|
|
2.51.0
|
||
|
|
|
||
|
|
From 5e298d2d937b6da06500478be956abeb24357e05 Mon Sep 17 00:00:00 2001
|
||
|
|
From: Florian Weimer <fweimer@redhat.com>
|
||
|
|
Date: Fri, 1 Aug 2025 19:27:35 +0200
|
||
|
|
Subject: [PATCH] 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>
|
||
|
|
(cherry picked from commit 20681be149b9eb1b6c1f4246bf4bd801221c86cd)
|
||
|
|
---
|
||
|
|
NEWS | 1 +
|
||
|
|
elf/Makefile | 6 ++
|
||
|
|
elf/dl-find_object.c | 74 +++++++++++++---------
|
||
|
|
elf/dl-find_object.h | 7 ++-
|
||
|
|
elf/rtld.c | 25 +++++++-
|
||
|
|
elf/tst-link-map-contiguous-ldso.c | 98 ++++++++++++++++++++++++++++++
|
||
|
|
elf/tst-link-map-contiguous-libc.c | 57 +++++++++++++++++
|
||
|
|
elf/tst-link-map-contiguous-main.c | 45 ++++++++++++++
|
||
|
|
8 files changed, 281 insertions(+), 32 deletions(-)
|
||
|
|
create mode 100644 elf/tst-link-map-contiguous-ldso.c
|
||
|
|
create mode 100644 elf/tst-link-map-contiguous-libc.c
|
||
|
|
create mode 100644 elf/tst-link-map-contiguous-main.c
|
||
|
|
|
||
|
|
diff --git a/elf/Makefile b/elf/Makefile
|
||
|
|
index 48aa0b57e5..3a5596e2bb 100644
|
||
|
|
--- a/elf/Makefile
|
||
|
|
+++ b/elf/Makefile
|
||
|
|
@@ -543,6 +543,8 @@ tests-internal += \
|
||
|
|
tst-dl_find_object-threads \
|
||
|
|
tst-dlmopen2 \
|
||
|
|
tst-hash-collision3 \
|
||
|
|
+ tst-link-map-contiguous-ldso \
|
||
|
|
+ tst-link-map-contiguous-libc \
|
||
|
|
tst-ptrguard1 \
|
||
|
|
tst-stackguard1 \
|
||
|
|
tst-tls-surplus \
|
||
|
|
@@ -554,6 +556,10 @@ tests-internal += \
|
||
|
|
unload2 \
|
||
|
|
# 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 1e76373292..c9f4c1c8d1 100644
|
||
|
|
--- a/elf/dl-find_object.c
|
||
|
|
+++ b/elf/dl-find_object.c
|
||
|
|
@@ -465,6 +465,37 @@ _dl_find_object (void *pc1, struct dl_find_object *result)
|
||
|
|
}
|
||
|
|
rtld_hidden_def (_dl_find_object)
|
||
|
|
|
||
|
|
+/* 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
|
||
|
|
@@ -476,29 +507,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)
|
||
|
|
@@ -510,11 +520,18 @@ _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 && is_rtld_link_map (l))
|
||
|
|
+ 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)
|
||
|
|
{
|
||
|
|
@@ -764,7 +781,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 9aa2439eaa..d9d75c4ad9 100644
|
||
|
|
--- a/elf/dl-find_object.h
|
||
|
|
+++ b/elf/dl-find_object.h
|
||
|
|
@@ -94,7 +94,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)
|
||
|
|
@@ -141,8 +141,11 @@ _dl_find_object_from_map (struct link_map *l,
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (read_seg == 3)
|
||
|
|
- return;
|
||
|
|
+ goto done;
|
||
|
|
}
|
||
|
|
+
|
||
|
|
+ done:
|
||
|
|
+ l->l_find_object_processed = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Called by the dynamic linker to set up the data structures for the
|
||
|
|
diff --git a/elf/rtld.c b/elf/rtld.c
|
||
|
|
index 6fb900fb31..ef4d96c053 100644
|
||
|
|
--- a/elf/rtld.c
|
||
|
|
+++ b/elf/rtld.c
|
||
|
|
@@ -1241,7 +1241,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)
|
||
|
|
{
|
||
|
|
@@ -1259,6 +1259,29 @@ rtld_setup_phdr (void)
|
||
|
|
_dl_rtld_map.l_phnum = rtld_ehdr->e_phnum;
|
||
|
|
|
||
|
|
|
||
|
|
+ _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 (_dl_rtld_map.l_contiguous && expected_load_address != 0
|
||
|
|
+ && expected_load_address != mapstart)
|
||
|
|
+ _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 0000000000..04de808bb2
|
||
|
|
--- /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 0000000000..eb5728c765
|
||
|
|
--- /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 0000000000..2d1a054f0f
|
||
|
|
--- /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>
|
||
|
|
--
|
||
|
|
2.51.0
|
||
|
|
|