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