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

Message ID 20211210183709.3906390-1-hjl.tools@gmail.com
State Superseded
Headers
Series [v5] 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. 10, 2021, 6:37 p.m. UTC
  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 DT_DEBUG 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' without entry point
$

instead of

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

This fixes [BZ #28453].
---
 elf/Makefile            | 11 +++++++++++
 elf/rtld.c              | 37 ++++++++++++++++++++++++++-----------
 elf/tst-rtld-run-dso.sh | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 70 insertions(+), 11 deletions(-)
 create mode 100755 elf/tst-rtld-run-dso.sh
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index ef36008673..2b85bfa03b 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -50,6 +50,10 @@  ifeq (yesyes,$(build-shared)$(run-built-tests))
 tests-special += $(objpfx)list-tunables.out
 endif
 
+ifeq (yes,$(build-shared))
+tests-special += $(objpfx)tst-rtld-run-dso.out
+endif
+
 # Make sure that the compiler does not insert any library calls in tunables
 # code paths.
 ifeq (yes,$(have-loop-to-function))
@@ -1877,6 +1881,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/rtld.c b/elf/rtld.c
index 6ce1e07dc0..6db3c62c80 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1108,8 +1108,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 +1123,23 @@  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 DT_DEBUG entry in PT_DYNAMIC segment, it is a shared
+     library.  */
+  if (has_pt_dynamic
+      && __glibc_unlikely (main_map->l_info[DT_DEBUG] == NULL))
+    return false;
 
+  /* This is a static executable.  */
   const char *pathname = _dl_argv[0];
   if (argv0 != NULL)
     _dl_argv[0] = argv0;
@@ -1144,6 +1151,7 @@  rtld_chain_load (struct link_map *main_map, char *argv0)
   else
     _dl_fatal_printf("%s: cannot execute %s: %d\n",
 		     rtld_soname, pathname, errno);
+  return true;
 }
 
 static void
@@ -1181,6 +1189,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 +1425,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;
@@ -2491,6 +2501,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"