]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
elf: Handle ld.so with LOAD segment gaps in _dl_find_object (bug 31943) release/2.38/master
authorFlorian Weimer <fweimer@redhat.com>
Fri, 1 Aug 2025 10:19:49 +0000 (12:19 +0200)
committerFlorian Weimer <fweimer@redhat.com>
Thu, 14 Aug 2025 07:08:15 +0000 (09:08 +0200)
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
elf/Makefile
elf/dl-find_object.c
elf/dl-find_object.h
elf/rtld.c
elf/tst-link-map-contiguous-ldso.c [new file with mode: 0644]
elf/tst-link-map-contiguous-libc.c [new file with mode: 0644]
elf/tst-link-map-contiguous-main.c [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index cbc15745f5902d823087c7f397a3d19c041b1644..41c48e1997d4e5c96553c3e67b3788282b678a5f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -60,6 +60,7 @@ The following bugs are resolved with this release:
   [31185] Incorrect thread point access in _dl_tlsdesc_undefweak and _dl_tlsdesc_dynamic
   [31476] resolv: Track single-request fallback via _res._flags
   [31890] resolv: Allow short error responses to match any DNS query
+  [31943] _dl_find_object can fail if ld.so contains gaps between load segments
   [31965] rseq extension mechanism does not work as intended
   [31968] mremap implementation in C does not handle arguments correctly
   [32052] Name space violation in fortify wrappers
index ff14e80900e135c8fcfd3a492887c1f302eae3ed..098d9a15b7deee4eba38f02f5299c5abf70767a2 100644 (file)
@@ -497,6 +497,8 @@ tests-internal += \
   tst-dl_find_object \
   tst-dl_find_object-threads \
   tst-dlmopen2 \
+  tst-link-map-contiguous-ldso \
+  tst-link-map-contiguous-libc \
   tst-ptrguard1 \
   tst-stackguard1 \
   tst-tls-surplus \
@@ -508,6 +510,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 \
index c1390ee10f3028793848ccf2d2e794c6efb2b2de..d750d58bfaa1b1f7f066c4cc6d042a91dc689adb 100644 (file)
@@ -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,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)
             {
@@ -756,7 +777,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;
         }
 
index 87c94606191f3069a5b4e2f385d031b962340606..b6ce6140be0c21d84e37d20be5d51456dd04356e 100644 (file)
@@ -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)
index 8b7b0ff4841cee1f92c3ede3b01d4f376d2c1852..e1c7e7fcfb6cc622e94fa307bb920afa7af71ee1 100644 (file)
@@ -1292,7 +1292,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)
 {
@@ -1310,6 +1310,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 (file)
index 0000000..04de808
--- /dev/null
@@ -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 (file)
index 0000000..eb5728c
--- /dev/null
@@ -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 (file)
index 0000000..2d1a054
--- /dev/null
@@ -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>