[PATCH v5 23/23] elf: Handle ld.so with LOAD segment gaps in _dl_find_object (bug 31943)
Florian Weimer
fweimer@redhat.com
Fri May 16 15:33:50 GMT 2025
If the architecture supports multiple page sizes, compile
in support code that checks for gaps between load segments. Otherwise,
assume that ld does not produce such binaries.
---
elf/dl-find_object.c | 77 ++++++++++++++++++++++++++++----------------
elf/rtld.c | 28 +++++++++++++++-
2 files changed, 76 insertions(+), 29 deletions(-)
diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
index 1e76373292..389b20c70d 100644
--- a/elf/dl-find_object.c
+++ b/elf/dl-find_object.c
@@ -26,6 +26,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <sys/pagesize.h>
/* Fallback implementation of _dl_find_object. It uses a linear
search, needs locking, and is not async-signal-safe. It is used in
@@ -465,6 +466,38 @@ _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;
+ _dlfo_nodelete_mappings[nodelete].map_start
+ = ph->p_vaddr + map->l_addr;
+ _dlfo_nodelete_mappings[nodelete].map_end
+ = _dlfo_nodelete_mappings[nodelete].map_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 +509,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 +522,20 @@ _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;
+#if defined SHARED && PAGE_SIZE_MIN != PAGE_SIZE_MAX
+ /* The kernel may have loaded ld.so with gaps. */
+ if (!l->l_contiguous && l == &_dl_rtld_map)
+ nodelete
+ = _dlfo_process_initial_noncontiguous_map (l, nodelete);
+ else
+#endif /* Multiple page sizes. */
+ {
+ 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)
{
diff --git a/elf/rtld.c b/elf/rtld.c
index 7301c80a13..0847a8f1a3 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -52,6 +52,7 @@
#include <dl-find_object.h>
#include <dl-audit-check.h>
#include <dl-call_tls_init_tp.h>
+#include <sys/pagesize.h>
#include <assert.h>
@@ -1240,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)
{
@@ -1258,6 +1259,31 @@ rtld_setup_phdr (void)
_dl_rtld_map.l_phnum = rtld_ehdr->e_phnum;
+ _dl_rtld_map.l_contiguous = 1;
+#if PAGE_SIZE_MIN != PAGE_SIZE_MAX
+ /* 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));
+ }
+ }
+#endif /* Support for multiple page sizes. */
+
/* PT_GNU_RELRO is usually the last phdr. */
size_t cnt = rtld_ehdr->e_phnum;
while (cnt-- > 0)
--
2.49.0
More information about the Libc-alpha
mailing list