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