]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
elf: Handle ld.so with LOAD segment gaps in _dl_find_object (bug 31943) fw/bug31943-with-test
authorFlorian Weimer <fweimer@redhat.com>
Tue, 2 Jul 2024 11:19:13 +0000 (13:19 +0200)
committerFlorian Weimer <fweimer@redhat.com>
Mon, 8 Jul 2024 13:28:00 +0000 (15:28 +0200)
config.h.in
config.make.in
configure
configure.ac
dlfcn/tst-dlinfo-phdr.c
elf/Makefile
elf/dl-find_object.c
elf/rtld.c
elf/tst-load-segment-gaps.py [new file with mode: 0644]

index 9a83b774fab74d6fa55376cbb31df23801781a46..c8a449d620903a5be6271a2eb60d2b7d4934f0d7 100644 (file)
    any external dependencies such as making a function call.  */
 #define HAVE_BUILTIN_TRAP 0
 
+/* Define if ld may produce gaps in load segments.  */
+#undef HAVE_LD_LOAD_GAPS
+
 /* ports/sysdeps/mips/configure.in  */
 /* Define if using the IEEE 754-2008 NaN encoding on the MIPS target.  */
 #undef HAVE_MIPS_NAN2008
index 55e8b7563b961dfc4278c2de43e89b9f51583593..831db714c1985a0ce6a03655d1e4dc2b9853690c 100644 (file)
@@ -98,6 +98,7 @@ CXX = @CXX@
 BUILD_CC = @BUILD_CC@
 CFLAGS = @CFLAGS@
 CPPFLAGS-config = @CPPFLAGS@
+have-ld-load-gaps = @with_ld_load_gaps@
 extra-nonshared-cflags = @extra_nonshared_cflags@
 rtld-early-cflags = @rtld_early_cflags@
 ASFLAGS-config = @ASFLAGS_config@
index 1bae55b45b25b251148f852abf8f4fadfeff971b..4685b062035f9e5338cb996d649d80578c892173 100755 (executable)
--- a/configure
+++ b/configure
@@ -610,6 +610,7 @@ PACKAGE_URL='https://www.gnu.org/software/glibc/'
 
 ac_unique_file="include/features.h"
 enable_option_checking=no
+with_ld_load_gaps=check
 ac_subst_vars='LTLIBOBJS
 LIBOBJS
 pthread_in_libc
@@ -679,6 +680,7 @@ SED
 MAKEINFO
 MSGFMT
 MAKE
+with_ld_load_gaps
 LD
 NM
 OBJDUMP
@@ -807,6 +809,7 @@ enable_cet
 enable_scv
 enable_fortify_source
 with_cpu
+with_ld_load_gaps
 '
       ac_precious_vars='build_alias
 host_alias
@@ -1509,6 +1512,8 @@ Optional Packages:
   --with-timeoutfactor=NUM
                           specify an integer to scale the timeout
   --with-cpu=CPU          select code for CPU variant
+  --with-ld-load-gaps     support linker with LOAD segment gaps bug
+                          [default=check]
 
 Some influential environment variables:
   CC          C compiler command
@@ -5302,6 +5307,39 @@ esac
 config_vars="$config_vars
 with-lld = $libc_cv_with_lld"
 
+
+# Check whether --with-ld_load_gaps was given.
+if test ${with_ld_load_gaps+y}
+then :
+  withval=$with_ld_load_gaps;
+else case e in #(
+  e) :  ;;
+esac
+fi
+
+if test "x$with_ld_load_gaps" = xcheck
+then :
+  echo "Checking binutils ld version:" >&5
+   if LC_ALL=C $LD --version | grep -E '^GNU ld version 2\.(2[0-9]|3[0-8])[^0-9]' >&5
+then :
+  with_ld_load_gaps=yes
+else case e in #(
+  e) with_ld_load_gaps=no
+        echo "(linker not binutils or not impacted)" >&5 ;;
+esac
+fi
+fi
+if test "x$with_ld_load_gaps" != xyes && test "x$with_ld_load_gaps" != xno
+then :
+  as_fn_error $? "invalid --with-ld-load-gaps argument: $with_ld_load_gaps" "$LINENO" 5
+fi
+if test "x$with_ld_load_gaps" = xyes
+then :
+  printf "%s\n" "#define HAVE_LD_LOAD_GAPS 1" >>confdefs.h
+
+fi
+
+
 # These programs are version sensitive.
 for ac_prog in gnumake gmake make
 do
index e48957f318055e6c3d68226f8d1def2e61790910..4a952c100156267ef11992456a7cc7423b581305 100644 (file)
@@ -526,6 +526,26 @@ case $($LD --version) in
 esac
 LIBC_CONFIG_VAR([with-lld], [$libc_cv_with_lld])
 
+dnl Workaround for binutils LOAD segment gaps bug (swbz#28743)
+dnl Fixed in commit 9833b7757d246f22db4eb24b8e5db7eb5e05b6d9
+dnl ("PR28824, relro security issues"), part of binutils 2.39.
+AC_ARG_WITH([ld_load_gaps],
+  [AS_HELP_STRING([--with-ld-load-gaps],
+    [support linker with LOAD segment gaps bug @<:@default=check@:>@])],
+  [],
+  [: m4_divert_text([DEFAULTS], [with_ld_load_gaps=check])])
+AS_IF([test "x$with_ld_load_gaps" = xcheck],
+  [echo "Checking binutils ld version:" >&AS_MESSAGE_LOG_FD
+   AS_IF([LC_ALL=C $LD --version | grep -E '^GNU ld version 2\.(2[[0-9]]|3[[0-8]])[[^0-9]]' >&AS_MESSAGE_LOG_FD],
+        [with_ld_load_gaps=yes],
+       [with_ld_load_gaps=no
+        echo "(linker not binutils or not impacted)" >&AS_MESSAGE_LOG_FD])])
+AS_IF([test "x$with_ld_load_gaps" != xyes && test "x$with_ld_load_gaps" != xno],
+  AC_MSG_ERROR([invalid --with-ld-load-gaps argument: $with_ld_load_gaps]))
+AS_IF([test "x$with_ld_load_gaps" = xyes],
+  [AC_DEFINE(HAVE_LD_LOAD_GAPS)])
+AC_SUBST(with_ld_load_gaps)
+
 # These programs are version sensitive.
 AC_CHECK_PROG_VER(MAKE, gnumake gmake make, --version,
   [GNU Make[^0-9]*\([0-9][0-9.]*\)],
index fdffb17724045783d9d8d71fdbf87ae9ce21eefd..7da525a1e34d62dc4015b29435f8a51643f49293 100644 (file)
@@ -64,8 +64,8 @@ do_test (void)
 
   do
     {
-      printf ("info: checking link map %p (%p) for \"%s\"\n",
-              l, l->l_phdr, l->l_name);
+      printf ("info: checking link map %p (%p %p) for \"%s\"\n",
+              l, l->l_phdr, (void *) l->l_addr, l->l_name);
 
       /* Cause dlerror () to return an error message.  */
       dlsym (RTLD_DEFAULT, "does-not-exist");
index a3475f3fb54a6ae726ff63c6004902fb993090ce..a9fd97e96ef2daba1fcc407e7a60d250305d5a3e 100644 (file)
@@ -615,6 +615,14 @@ $(objpfx)tst-relro-libc.out: tst-relro-symbols.py $(..)/scripts/glibcelf.py \
            --required=_IO_wfile_jumps \
            --required=__io_vtables \
          > $@ 2>&1; $(evaluate-test)
+tests-special += $(objpfx)tst-gaps-ldso.out
+$(objpfx)tst-gaps-ldso.out: tst-load-segment-gaps.py \
+  $(..)/scripts/glibcelf.py $(objpfx)ld.so
+       $(PYTHON) tst-load-segment-gaps.py $(objpfx)ld.so \
+         > $@ 2>&1; $(evaluate-test)
+ifeq ($(have-ld-load-gaps),yes)
+test-xfail-tst-gaps-ldso = yes
+endif
 
 ifeq ($(run-built-tests),yes)
 tests-special += $(objpfx)tst-valgrind-smoke.out
index 449302eda35ce96fec45f6e871baf8ca8b74c12c..9b63e7bd92f5ece88bb37dd1ad43904bbc1f8c49 100644 (file)
@@ -466,6 +466,38 @@ __dl_find_object (void *pc1, struct dl_find_object *result)
 hidden_def (__dl_find_object)
 weak_alias (__dl_find_object, _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
@@ -477,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)
@@ -511,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 HAVE_LD_LOAD_GAPS && defined SHARED
+              /* The kernel may have loaded ld.so with gaps.   */
+              if (!l->l_contiguous && l == &GL(dl_rtld_map))
+                nodelete
+                  = _dlfo_process_initial_noncontiguous_map (l, nodelete);
+              else
+#endif
+                {
+                  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)
             {
index bfdf632e77f2a1e1f03832690da59cf5cee8ae51..337901007d34eeb68926c4889ab9f78d239b64b4 100644 (file)
@@ -1766,6 +1766,30 @@ dl_main (const ElfW(Phdr) *phdr,
 
   GL(dl_rtld_map).l_phdr = rtld_phdr;
   GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
+  GL(dl_rtld_map).l_contiguous = 1;
+#ifdef HAVE_LD_LOAD_GAPS
+  /* 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.  */
+  {
+    ElfW(Addr) expected_load_address = 0;
+    for (const ElfW(Phdr) *ph = rtld_phdr; ph < &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));
+       }
+  }
+#endif
 
 
   /* PT_GNU_RELRO is usually the last phdr.  */
diff --git a/elf/tst-load-segment-gaps.py b/elf/tst-load-segment-gaps.py
new file mode 100644 (file)
index 0000000..85ae734
--- /dev/null
@@ -0,0 +1,74 @@
+#!/usr/bin/python3
+# Verify that objects do not contain gaps in load segments.
+# Copyright (C) 2024 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/>.
+
+import argparse
+import os.path
+import sys
+
+# Make available glibc Python modules.
+sys.path.append(os.path.join(
+    os.path.dirname(os.path.realpath(__file__)), os.path.pardir, 'scripts'))
+
+import glibcelf
+
+def rounddown(val, align):
+    assert (align & (align - 1)) == 0, align
+    return val & -align
+def roundup(val, align):
+    assert (align & (align - 1)) == 0, align
+    return (val + align - 1) & -align
+
+errors = False
+
+def process(path, img):
+    global errors
+    loads = [phdr for phdr in img.phdrs()
+             if phdr.p_type == glibcelf.Pt.PT_LOAD]
+    if not loads:
+        # Nothing ot check.
+        return
+    alignments = [phdr.p_align for phdr in loads if phdr.p_align > 0]
+    if alignments:
+        align = min(alignments)
+    else:
+        print('error: cannot infer page size')
+        errors = True
+        align = 4096
+    print('info: inferred page size:', align)
+    current_address = None
+    for idx, phdr in enumerate(loads):
+        this_address = rounddown(phdr.p_vaddr, align)
+        next_address = roundup(phdr.p_vaddr + phdr.p_memsz, align)
+        print('info: LOAD segment {}: address 0x{:x}, size {},'
+              ' range [0x{:x},0x{:x})'
+               .format(idx, phdr.p_vaddr, phdr.p_memsz,
+                       this_address, next_address))
+        if current_address is not None:
+            gap = this_address - current_address
+            if gap != 0:
+                errors = True
+                print('error: gap between load segments: {} bytes'.format(gap))
+        current_address = next_address
+
+for path in sys.argv[1:]:
+    img = glibcelf.Image.readfile(path)
+    process(path, img)
+
+if errors:
+    sys.exit(1)