[v7] elf: Don't execute shared object directly [BZ #28453]

Message ID 20211211200705.3973-1-hjl.tools@gmail.com
State Committed
Headers
Series [v7] elf: Don't execute shared object directly [BZ #28453] |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent
dj/TryBot-32bit success Build for i686

Commit Message

H.J. Lu Dec. 11, 2021, 8:07 p.m. UTC
  Changes in the v7 patch:

1. Check !elf_has_debug_entry_p instead of elf_has_debug_entry_p.
2. Fix typos in sysdeps/mips/dl-debug.h.
3. Run the tst-rtld-run-dso test only if $(run-built-tests) == yes.

Changes in the v6 patch:

1. Remove ELF_MACHINE_DEBUG_SETUP.
2. Add <dl-debug.h> to setup and check debugging entry in PT_DYNAMIC
segment to support DT_DEBUG, DT_MIPS_RLD_MAP_REL and DT_MIPS_RLD_MAP.

Changes in the v5 patch:

1. Don't check DT_NEEDED since not all shared libraries have DT_NEEDED.
2. Update tst-rtld-run-dso test to use tst-ro-dynamic-mod.so which
doesn't have DT_NEEDED.
3. Check DT_DEBUG entry in PT_DYNAMIC segment to detect shared libraries.

Changes in the v4 patch:

1. Only allow executing executables and libc.s.6 directly.

Changes in the v3 patch:

1. Delay zero entry point value check.
2. Build testobj1.so with -Wl,--entry=0

Changes in the v2 patch:

1. Use rtld_progname in the error message.

A shared library can have an invalid non-zero entry point value generated
by the old linkers, which leads to ld.so crash when it executes such
shared library directly.  Execute an ELF binary directly only if

1. It has a PT_INTERP segment, which covers dynamically linked
executables and libc.so.  Or
2. It doesn't have a PT_DYNAMIC segment, which covers static non-PIE
executables.  Or
3. It has debugging entry in PT_DYNAMIC segment, which covers static PIE
executables.

Now we get

$ ./elf/ld.so ./libc.so.6
GNU C Library (GNU libc) development release version 2.34.9000.
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.1 20211203 (Red Hat 11.2.1-7).
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://www.gnu.org/software/libc/bugs.html>.
$ ./elf/ld.so /lib64/libstdc++.so.6.0.29
./elf/ld.so: cannot execute '/lib64/libstdc++.so.6.0.29' directly
$

instead of

$ /lib64/ld-linux-x86-64.so.2 /lib64/libstdc++.so.6.0.29
Segmentation fault (core dumped)
$

Also simplify debugging entry processing in PT_DYNAMIC segment:

1. Remove ELF_MACHINE_DEBUG_SETUP.
2. Add <dl-debug.h> to setup and check debugging entry in PT_DYNAMIC
segment to support DT_DEBUG, DT_MIPS_RLD_MAP_REL and DT_MIPS_RLD_MAP.

This fixes [BZ #28453].

Tested on x86-64, x32 and i686 as well as with build-many-glibcs.py.
---
 elf/Makefile               | 10 +++++++-
 elf/dl-reloc-static-pie.c  | 11 ++------
 elf/rtld.c                 | 49 ++++++++++++++++++++---------------
 elf/tst-rtld-run-dso.sh    | 33 ++++++++++++++++++++++++
 sysdeps/generic/dl-debug.h | 42 ++++++++++++++++++++++++++++++
 sysdeps/mips/dl-debug.h    | 52 ++++++++++++++++++++++++++++++++++++++
 sysdeps/mips/dl-machine.h  | 15 -----------
 7 files changed, 167 insertions(+), 45 deletions(-)
 create mode 100755 elf/tst-rtld-run-dso.sh
 create mode 100644 sysdeps/generic/dl-debug.h
 create mode 100644 sysdeps/mips/dl-debug.h
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index fe42caeb0e..1337ec2d0b 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -47,7 +47,8 @@  tunables-type = $(addprefix TUNABLES_FRONTEND_,$(have-tunables))
 CPPFLAGS-dl-tunables.c += -DTUNABLES_FRONTEND=$(tunables-type)
 
 ifeq (yesyes,$(build-shared)$(run-built-tests))
-tests-special += $(objpfx)list-tunables.out
+tests-special += $(objpfx)list-tunables.out \
+  $(objpfx)tst-rtld-run-dso.out
 endif
 
 # Make sure that the compiler does not insert any library calls in tunables
@@ -1892,6 +1893,13 @@  $(objpfx)list-tunables.out: tst-rtld-list-tunables.sh $(objpfx)ld.so
 	    $(objpfx)/tst-rtld-list-tunables.out > $@; \
 	$(evaluate-test)
 
+$(objpfx)tst-rtld-run-dso.out: tst-rtld-run-dso.sh $(objpfx)ld.so \
+			    $(objpfx)tst-ro-dynamic-mod.so
+	$(SHELL) tst-rtld-run-dso.sh $(objpfx)ld.so \
+	    $(objpfx)tst-ro-dynamic-mod.so \
+	    '$(test-wrapper-env)' '$(run_program_env)' > $@
+	$(evaluate-test)
+
 tst-dst-static-ENV = LD_LIBRARY_PATH='$$ORIGIN'
 
 $(objpfx)tst-rtld-help.out: $(objpfx)ld.so
diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
index 5b85df8a2e..ad91721fe3 100644
--- a/elf/dl-reloc-static-pie.c
+++ b/elf/dl-reloc-static-pie.c
@@ -24,6 +24,7 @@ 
 #include <ldsodefs.h>
 
 #include <dl-machine.h>
+#include <dl-debug.h>
 
 #define RESOLVE_MAP(map, scope, sym, version, flags) map
 #include "dynamic-link.h"
@@ -68,14 +69,6 @@  _dl_relocate_static_pie (void)
 
   /* Set up debugging before the debugger is notified for the first
      time.  */
-# ifdef ELF_MACHINE_DEBUG_SETUP
-  /* Some machines (e.g. MIPS) don't use DT_DEBUG in this way.  */
-  ELF_MACHINE_DEBUG_SETUP (main_map, r);
-# else
-  if (main_map->l_info[DT_DEBUG] != NULL)
-    /* There is a DT_DEBUG entry in the dynamic section.  Fill it in
-       with the run-time address of the r_debug structure  */
-    main_map->l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r;
-# endif
+  elf_setup_debug_entry (main_map, r);
 }
 #endif
diff --git a/elf/rtld.c b/elf/rtld.c
index 4b09e84b0d..e1b3ccce76 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -62,6 +62,9 @@ 
 #define RESOLVE_MAP(map, scope, sym, version, flags) map
 #include "dynamic-link.h"
 
+/* Must include after <dl-machine.h> for DT_MIPS definition.  */
+#include <dl-debug.h>
+
 /* Only enables rtld profiling for architectures which provides non generic
    hp-timing support.  The generic support requires either syscall
    (clock_gettime), which will incur in extra overhead on loading time.
@@ -1108,8 +1111,9 @@  load_audit_modules (struct link_map *main_map, struct audit_list *audit_list)
 }
 
 /* Check if the executable is not actualy dynamically linked, and
-   invoke it directly in that case.  */
-static void
+   invoke it directly in that case.  Return true if it can be executed
+   directly by ld.so.  */
+static bool
 rtld_chain_load (struct link_map *main_map, char *argv0)
 {
   /* The dynamic loader run against itself.  */
@@ -1122,17 +1126,22 @@  rtld_chain_load (struct link_map *main_map, char *argv0)
 		  + main_map->l_info[DT_SONAME]->d_un.d_val)) == 0)
     _dl_fatal_printf ("%s: loader cannot load itself\n", rtld_soname);
 
-  /* With DT_NEEDED dependencies, the executable is dynamically
-     linked.  */
-  if (__glibc_unlikely (main_map->l_info[DT_NEEDED] != NULL))
-    return;
-
-  /* If the executable has program interpreter, it is dynamically
-     linked.  */
+  /* If it has program interpreter, it can be executed directly by
+     ld.so.  Otherwise, it must be a static executable or a shared
+     library.  */
+  bool has_pt_dynamic = false;
   for (size_t i = 0; i < main_map->l_phnum; ++i)
     if (main_map->l_phdr[i].p_type == PT_INTERP)
-      return;
+      return true;
+    else if (main_map->l_phdr[i].p_type == PT_DYNAMIC)
+      has_pt_dynamic = true;
 
+  /* If there is no debugging entry in PT_DYNAMIC segment, it is a
+     shared library.  */
+  if (has_pt_dynamic && !elf_has_debug_entry_p (main_map))
+    return false;
+
+  /* This is a static executable.  */
   const char *pathname = _dl_argv[0];
   if (argv0 != NULL)
     _dl_argv[0] = argv0;
@@ -1144,6 +1153,7 @@  rtld_chain_load (struct link_map *main_map, char *argv0)
   else
     _dl_fatal_printf("%s: cannot execute %s: %d\n",
 		     rtld_soname, pathname, errcode);
+  return true;
 }
 
 static void
@@ -1181,6 +1191,8 @@  dl_main (const ElfW(Phdr) *phdr,
   _dl_starting_up = 1;
 #endif
 
+  bool can_execute = true;
+
   const char *ld_so_name = _dl_argv[0];
   if (*user_entry == (ElfW(Addr)) ENTRY_POINT)
     {
@@ -1415,7 +1427,7 @@  dl_main (const ElfW(Phdr) *phdr,
       main_map = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
 
       if (__glibc_likely (state.mode == rtld_mode_normal))
-	rtld_chain_load (main_map, argv0);
+	can_execute = rtld_chain_load (main_map, argv0);
 
       phdr = main_map->l_phdr;
       phnum = main_map->l_phnum;
@@ -1796,15 +1808,7 @@  dl_main (const ElfW(Phdr) *phdr,
   size_t count_modids = _dl_count_modids ();
 
   /* Set up debugging before the debugger is notified for the first time.  */
-#ifdef ELF_MACHINE_DEBUG_SETUP
-  /* Some machines (e.g. MIPS) don't use DT_DEBUG in this way.  */
-  ELF_MACHINE_DEBUG_SETUP (main_map, r);
-#else
-  if (main_map->l_info[DT_DEBUG] != NULL)
-    /* There is a DT_DEBUG entry in the dynamic section.  Fill it in
-       with the run-time address of the r_debug structure  */
-    main_map->l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r;
-#endif
+  elf_setup_debug_entry (main_map, r);
 
   /* We start adding objects.  */
   r->r_state = RT_ADD;
@@ -2491,6 +2495,11 @@  dl_main (const ElfW(Phdr) *phdr,
       rtld_timer_accum (&relocate_time, start);
     }
 
+  /* Stop if it can't be executed.  */
+  if (!can_execute)
+    _dl_fatal_printf ("%s: cannot execute shared object '%s' directly\n",
+		      ld_so_name, rtld_progname);
+
   /* Relocation is complete.  Perform early libc initialization.  This
      is the initial libc, even if audit modules have been loaded with
      other libcs.  */
diff --git a/elf/tst-rtld-run-dso.sh b/elf/tst-rtld-run-dso.sh
new file mode 100755
index 0000000000..5192f64210
--- /dev/null
+++ b/elf/tst-rtld-run-dso.sh
@@ -0,0 +1,33 @@ 
+#!/bin/sh
+# Test for ld.so on a shared library with no associated entry point.
+# Copyright (C) 2021 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/>.
+
+set -e
+
+rtld=$1
+dso=$2
+test_wrapper_env=$3
+run_program_env=$4
+
+LC_ALL=C
+export LC_ALL
+
+${test_wrapper_env} \
+${run_program_env} \
+$rtld $dso 2>&1 \
+| grep "cannot execute"
diff --git a/sysdeps/generic/dl-debug.h b/sysdeps/generic/dl-debug.h
new file mode 100644
index 0000000000..87a3ccf795
--- /dev/null
+++ b/sysdeps/generic/dl-debug.h
@@ -0,0 +1,42 @@ 
+/* Debugging support.  Generic version.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef _DL_DEBUG_H
+#define _DL_DEBUG_H
+
+/* There is a DT_DEBUG entry in the dynamic section.  Fill it in with the
+   run-time address of the r_debug structure  */
+
+static inline void
+__attribute ((always_inline))
+elf_setup_debug_entry (struct link_map *l, struct r_debug *r)
+{
+  if (l->l_info[DT_DEBUG] != NULL)
+    l->l_info[DT_DEBUG]->d_un.d_ptr = (ElfW(Addr)) r;
+}
+
+/* Return true if there is a DT_DEBUG entry in the dynamic section.  */
+
+static inline bool
+__attribute ((always_inline))
+elf_has_debug_entry_p (struct link_map *l)
+{
+  return l->l_info[DT_DEBUG] != NULL;
+}
+
+#endif /* _DL_DEBUG_H */
diff --git a/sysdeps/mips/dl-debug.h b/sysdeps/mips/dl-debug.h
new file mode 100644
index 0000000000..85b2920a4b
--- /dev/null
+++ b/sysdeps/mips/dl-debug.h
@@ -0,0 +1,52 @@ 
+/* Debugging support.  MIPS version.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef _DL_DEBUG_H
+#define _DL_DEBUG_H
+
+/* If there is a DT_MIPS_RLD_MAP_REL or DT_MIPS_RLD_MAP entry in the
+   dynamic section, fill in the debug map pointer with the run-time
+   address of the r_debug structure.  */
+
+static inline void
+__attribute ((always_inline))
+elf_setup_debug_entry (struct link_map *l, struct r_debug *r)
+{
+  if (l->l_info[DT_MIPS (RLD_MAP_REL)] != NULL)
+    {
+      char *ptr = (char *) l->l_info[DT_MIPS (RLD_MAP_REL)];
+      ptr += l->l_info[DT_MIPS (RLD_MAP_REL)]->d_un.d_val;
+      *(ElfW(Addr) *) ptr = (ElfW(Addr)) r;
+    }
+  else if (l->l_info[DT_MIPS (RLD_MAP)] != NULL)
+    *(ElfW(Addr) *) (l->l_info[DT_MIPS (RLD_MAP)]->d_un.d_ptr)
+      = (ElfW(Addr)) r;
+}
+
+/* Return true if there is a DT_MIPS_RLD_MAP_REL or DT_MIPS_RLD_MAP entry
+   in the dynamic section.  */
+
+static inline bool
+__attribute ((always_inline))
+elf_has_debug_entry_p (struct link_map *l)
+{
+  return (l->l_info[DT_MIPS (RLD_MAP_REL)] != NULL
+	  || l->l_info[DT_MIPS (RLD_MAP)] != NULL);
+}
+
+#endif /* _DL_DEBUG_H */
diff --git a/sysdeps/mips/dl-machine.h b/sysdeps/mips/dl-machine.h
index d7b8341b74..ea8c881807 100644
--- a/sysdeps/mips/dl-machine.h
+++ b/sysdeps/mips/dl-machine.h
@@ -65,21 +65,6 @@ 
    in l_info array.  */
 #define DT_MIPS(x) (DT_MIPS_##x - DT_LOPROC + DT_NUM)
 
-/* If there is a DT_MIPS_RLD_MAP_REL or DT_MIPS_RLD_MAP entry in the dynamic
-   section, fill in the debug map pointer with the run-time address of the
-   r_debug structure.  */
-#define ELF_MACHINE_DEBUG_SETUP(l,r) \
-do { if ((l)->l_info[DT_MIPS (RLD_MAP_REL)]) \
-       { \
-	 char *ptr = (char *)(l)->l_info[DT_MIPS (RLD_MAP_REL)]; \
-	 ptr += (l)->l_info[DT_MIPS (RLD_MAP_REL)]->d_un.d_val; \
-	 *(ElfW(Addr) *)ptr = (ElfW(Addr)) (r); \
-       } \
-     else if ((l)->l_info[DT_MIPS (RLD_MAP)]) \
-       *(ElfW(Addr) *)((l)->l_info[DT_MIPS (RLD_MAP)]->d_un.d_ptr) = \
-       (ElfW(Addr)) (r); \
-   } while (0)
-
 #if ((defined __mips_nan2008 && !defined HAVE_MIPS_NAN2008) \
      || (!defined __mips_nan2008 && defined HAVE_MIPS_NAN2008))
 # error "Configuration inconsistency: __mips_nan2008 != HAVE_MIPS_NAN2008, overridden CFLAGS?"