4c1956
commit ad43cac44a6860eaefcadadfb2acb349921e96bf
4c1956
Author: Szabolcs Nagy <szabolcs.nagy@arm.com>
4c1956
Date:   Fri Jun 15 16:14:58 2018 +0100
4c1956
4c1956
    rtld: Use generic argv adjustment in ld.so [BZ #23293]
4c1956
    
4c1956
    When an executable is invoked as
4c1956
    
4c1956
      ./ld.so [ld.so-args] ./exe [exe-args]
4c1956
    
4c1956
    then the argv is adujusted in ld.so before calling the entry point of
4c1956
    the executable so ld.so args are not visible to it.  On most targets
4c1956
    this requires moving argv, env and auxv on the stack to ensure correct
4c1956
    stack alignment at the entry point.  This had several issues:
4c1956
    
4c1956
    - The code for this adjustment on the stack is written in asm as part
4c1956
      of the target specific ld.so _start code which is hard to maintain.
4c1956
    
4c1956
    - The adjustment is done after _dl_start returns, where it's too late
4c1956
      to update GLRO(dl_auxv), as it is already readonly, so it points to
4c1956
      memory that was clobbered by the adjustment. This is bug 23293.
4c1956
    
4c1956
    - _environ is also wrong in ld.so after the adjustment, but it is
4c1956
      likely not used after _dl_start returns so this is not user visible.
4c1956
    
4c1956
    - _dl_argv was updated, but for this it was moved out of relro, which
4c1956
      changes security properties across targets unnecessarily.
4c1956
    
4c1956
    This patch introduces a generic _dl_start_args_adjust function that
4c1956
    handles the argument adjustments after ld.so processed its own args
4c1956
    and before relro protection is applied.
4c1956
    
4c1956
    The same algorithm is used on all targets, _dl_skip_args is now 0, so
4c1956
    existing target specific adjustment code is no longer used.  The bug
4c1956
    affects aarch64, alpha, arc, arm, csky, ia64, nios2, s390-32 and sparc,
4c1956
    other targets don't need the change in principle, only for consistency.
4c1956
    
4c1956
    The GNU Hurd start code relied on _dl_skip_args after dl_main returned,
4c1956
    now it checks directly if args were adjusted and fixes the Hurd startup
4c1956
    data accordingly.
4c1956
    
4c1956
    Follow up patches can remove _dl_skip_args and DL_ARGV_NOT_RELRO.
4c1956
    
4c1956
    Tested on aarch64-linux-gnu and cross tested on i686-gnu.
4c1956
    
4c1956
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
4c1956
4c1956
diff --git a/elf/rtld.c b/elf/rtld.c
4c1956
index aee5ca357f66121e..22cceeab40319582 100644
4c1956
--- a/elf/rtld.c
4c1956
+++ b/elf/rtld.c
4c1956
@@ -1127,6 +1127,62 @@ rtld_chain_load (struct link_map *main_map, char *argv0)
4c1956
 		   rtld_soname, pathname, errcode);
4c1956
 }
4c1956
 
4c1956
+/* Adjusts the contents of the stack and related globals for the user
4c1956
+   entry point.  The ld.so processed skip_args arguments and bumped
4c1956
+   _dl_argv and _dl_argc accordingly.  Those arguments are removed from
4c1956
+   argv here.  */
4c1956
+static void
4c1956
+_dl_start_args_adjust (int skip_args)
4c1956
+{
4c1956
+  void **sp = (void **) (_dl_argv - skip_args - 1);
4c1956
+  void **p = sp + skip_args;
4c1956
+
4c1956
+  if (skip_args == 0)
4c1956
+    return;
4c1956
+
4c1956
+  /* Sanity check.  */
4c1956
+  intptr_t argc = (intptr_t) sp[0] - skip_args;
4c1956
+  assert (argc == _dl_argc);
4c1956
+
4c1956
+  /* Adjust argc on stack.  */
4c1956
+  sp[0] = (void *) (intptr_t) _dl_argc;
4c1956
+
4c1956
+  /* Update globals in rtld.  */
4c1956
+  _dl_argv -= skip_args;
4c1956
+  _environ -= skip_args;
4c1956
+
4c1956
+  /* Shuffle argv down.  */
4c1956
+  do
4c1956
+    *++sp = *++p;
4c1956
+  while (*p != NULL);
4c1956
+
4c1956
+  assert (_environ == (char **) (sp + 1));
4c1956
+
4c1956
+  /* Shuffle envp down.  */
4c1956
+  do
4c1956
+    *++sp = *++p;
4c1956
+  while (*p != NULL);
4c1956
+
4c1956
+#ifdef HAVE_AUX_VECTOR
4c1956
+  void **auxv = (void **) GLRO(dl_auxv) - skip_args;
4c1956
+  GLRO(dl_auxv) = (ElfW(auxv_t) *) auxv; /* Aliasing violation.  */
4c1956
+  assert (auxv == sp + 1);
4c1956
+
4c1956
+  /* Shuffle auxv down. */
4c1956
+  ElfW(auxv_t) ax;
4c1956
+  char *oldp = (char *) (p + 1);
4c1956
+  char *newp = (char *) (sp + 1);
4c1956
+  do
4c1956
+    {
4c1956
+      memcpy (&ax, oldp, sizeof (ax));
4c1956
+      memcpy (newp, &ax, sizeof (ax));
4c1956
+      oldp += sizeof (ax);
4c1956
+      newp += sizeof (ax);
4c1956
+    }
4c1956
+  while (ax.a_type != AT_NULL);
4c1956
+#endif
4c1956
+}
4c1956
+
4c1956
 static void
4c1956
 dl_main (const ElfW(Phdr) *phdr,
4c1956
 	 ElfW(Word) phnum,
4c1956
@@ -1185,6 +1241,7 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
       rtld_is_main = true;
4c1956
 
4c1956
       char *argv0 = NULL;
4c1956
+      char **orig_argv = _dl_argv;
4c1956
 
4c1956
       /* Note the place where the dynamic linker actually came from.  */
4c1956
       GL(dl_rtld_map).l_name = rtld_progname;
4c1956
@@ -1199,7 +1256,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 		GLRO(dl_lazy) = -1;
4c1956
 	      }
4c1956
 
4c1956
-	    ++_dl_skip_args;
4c1956
 	    --_dl_argc;
4c1956
 	    ++_dl_argv;
4c1956
 	  }
4c1956
@@ -1208,14 +1264,12 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	    if (state.mode != rtld_mode_help)
4c1956
 	      state.mode = rtld_mode_verify;
4c1956
 
4c1956
-	    ++_dl_skip_args;
4c1956
 	    --_dl_argc;
4c1956
 	    ++_dl_argv;
4c1956
 	  }
4c1956
 	else if (! strcmp (_dl_argv[1], "--inhibit-cache"))
4c1956
 	  {
4c1956
 	    GLRO(dl_inhibit_cache) = 1;
4c1956
-	    ++_dl_skip_args;
4c1956
 	    --_dl_argc;
4c1956
 	    ++_dl_argv;
4c1956
 	  }
4c1956
@@ -1225,7 +1279,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	    state.library_path = _dl_argv[2];
4c1956
 	    state.library_path_source = "--library-path";
4c1956
 
4c1956
-	    _dl_skip_args += 2;
4c1956
 	    _dl_argc -= 2;
4c1956
 	    _dl_argv += 2;
4c1956
 	  }
4c1956
@@ -1234,7 +1287,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	  {
4c1956
 	    GLRO(dl_inhibit_rpath) = _dl_argv[2];
4c1956
 
4c1956
-	    _dl_skip_args += 2;
4c1956
 	    _dl_argc -= 2;
4c1956
 	    _dl_argv += 2;
4c1956
 	  }
4c1956
@@ -1242,14 +1294,12 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	  {
4c1956
 	    audit_list_add_string (&state.audit_list, _dl_argv[2]);
4c1956
 
4c1956
-	    _dl_skip_args += 2;
4c1956
 	    _dl_argc -= 2;
4c1956
 	    _dl_argv += 2;
4c1956
 	  }
4c1956
 	else if (! strcmp (_dl_argv[1], "--preload") && _dl_argc > 2)
4c1956
 	  {
4c1956
 	    state.preloadarg = _dl_argv[2];
4c1956
-	    _dl_skip_args += 2;
4c1956
 	    _dl_argc -= 2;
4c1956
 	    _dl_argv += 2;
4c1956
 	  }
4c1956
@@ -1257,7 +1307,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	  {
4c1956
 	    argv0 = _dl_argv[2];
4c1956
 
4c1956
-	    _dl_skip_args += 2;
4c1956
 	    _dl_argc -= 2;
4c1956
 	    _dl_argv += 2;
4c1956
 	  }
4c1956
@@ -1265,7 +1314,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 		 && _dl_argc > 2)
4c1956
 	  {
4c1956
 	    state.glibc_hwcaps_prepend = _dl_argv[2];
4c1956
-	    _dl_skip_args += 2;
4c1956
 	    _dl_argc -= 2;
4c1956
 	    _dl_argv += 2;
4c1956
 	  }
4c1956
@@ -1273,7 +1321,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 		 && _dl_argc > 2)
4c1956
 	  {
4c1956
 	    state.glibc_hwcaps_mask = _dl_argv[2];
4c1956
-	    _dl_skip_args += 2;
4c1956
 	    _dl_argc -= 2;
4c1956
 	    _dl_argv += 2;
4c1956
 	  }
4c1956
@@ -1282,7 +1329,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	  {
4c1956
 	    state.mode = rtld_mode_list_tunables;
4c1956
 
4c1956
-	    ++_dl_skip_args;
4c1956
 	    --_dl_argc;
4c1956
 	    ++_dl_argv;
4c1956
 	  }
4c1956
@@ -1291,7 +1337,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	  {
4c1956
 	    state.mode = rtld_mode_list_diagnostics;
4c1956
 
4c1956
-	    ++_dl_skip_args;
4c1956
 	    --_dl_argc;
4c1956
 	    ++_dl_argv;
4c1956
 	  }
4c1956
@@ -1337,7 +1382,6 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
 	    _dl_usage (ld_so_name, NULL);
4c1956
 	}
4c1956
 
4c1956
-      ++_dl_skip_args;
4c1956
       --_dl_argc;
4c1956
       ++_dl_argv;
4c1956
 
4c1956
@@ -1433,6 +1477,9 @@ dl_main (const ElfW(Phdr) *phdr,
4c1956
       /* Set the argv[0] string now that we've processed the executable.  */
4c1956
       if (argv0 != NULL)
4c1956
         _dl_argv[0] = argv0;
4c1956
+
4c1956
+      /* Adjust arguments for the application entry point.  */
4c1956
+      _dl_start_args_adjust (_dl_argv - orig_argv);
4c1956
     }
4c1956
   else
4c1956
     {
4c1956
diff --git a/sysdeps/mach/hurd/dl-sysdep.c b/sysdeps/mach/hurd/dl-sysdep.c
4c1956
index 7bd1d70c96c229e0..8aab46bf6396c8d4 100644
4c1956
--- a/sysdeps/mach/hurd/dl-sysdep.c
4c1956
+++ b/sysdeps/mach/hurd/dl-sysdep.c
4c1956
@@ -107,6 +107,7 @@ _dl_sysdep_start (void **start_argptr,
4c1956
 {
4c1956
   void go (intptr_t *argdata)
4c1956
     {
4c1956
+      char *orig_argv0;
4c1956
       char **p;
4c1956
 
4c1956
       /* Cache the information in various global variables.  */
4c1956
@@ -115,6 +116,8 @@ _dl_sysdep_start (void **start_argptr,
4c1956
       _environ = &_dl_argv[_dl_argc + 1];
4c1956
       for (p = _environ; *p++;); /* Skip environ pointers and terminator.  */
4c1956
 
4c1956
+      orig_argv0 = _dl_argv[0];
4c1956
+
4c1956
       if ((void *) p == _dl_argv[0])
4c1956
 	{
4c1956
 	  static struct hurd_startup_data nodata;
4c1956
@@ -189,30 +192,23 @@ unfmh();			/* XXX */
4c1956
 
4c1956
       /* The call above might screw a few things up.
4c1956
 
4c1956
-	 First of all, if _dl_skip_args is nonzero, we are ignoring
4c1956
-	 the first few arguments.  However, if we have no Hurd startup
4c1956
-	 data, it is the magical convention that ARGV[0] == P.  The
4c1956
+	 P is the location after the terminating NULL of the list of
4c1956
+	 environment variables.  It has to point to the Hurd startup
4c1956
+	 data or if that's missing then P == ARGV[0] must hold. The
4c1956
 	 startup code in init-first.c will get confused if this is not
4c1956
 	 the case, so we must rearrange things to make it so.  We'll
4c1956
-	 overwrite the origional ARGV[0] at P with ARGV[_dl_skip_args].
4c1956
+	 recompute P and move the Hurd data or the new ARGV[0] there.
4c1956
 
4c1956
-	 Secondly, if we need to be secure, it removes some dangerous
4c1956
-	 environment variables.  If we have no Hurd startup date this
4c1956
-	 changes P (since that's the location after the terminating
4c1956
-	 NULL in the list of environment variables).  We do the same
4c1956
-	 thing as in the first case but make sure we recalculate P.
4c1956
-	 If we do have Hurd startup data, we have to move the data
4c1956
-	 such that it starts just after the terminating NULL in the
4c1956
-	 environment list.
4c1956
+	 Note: directly invoked ld.so can move arguments and env vars.
4c1956
 
4c1956
 	 We use memmove, since the locations might overlap.  */
4c1956
-      if (__libc_enable_secure || _dl_skip_args)
4c1956
-	{
4c1956
-	  char **newp;
4c1956
 
4c1956
-	  for (newp = _environ; *newp++;);
4c1956
+      char **newp;
4c1956
+      for (newp = _environ; *newp++;);
4c1956
 
4c1956
-	  if (_dl_argv[-_dl_skip_args] == (char *) p)
4c1956
+      if (newp != p || _dl_argv[0] != orig_argv0)
4c1956
+	{
4c1956
+	  if (orig_argv0 == (char *) p)
4c1956
 	    {
4c1956
 	      if ((char *) newp != _dl_argv[0])
4c1956
 		{