This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[PATCH] Add dynamic linker support for $EXEC_ORIGIN.
- From: Brooks Moses <bmoses at google dot com>
- To: libc-alpha at sourceware dot org
- Cc: "Carlos O'Donnell" <carlos at redhat dot com>, Paul Pluzhnikov <ppluzhnikov at google dot com>, Brooks Moses <bmoses at google dot com>
- Date: Mon, 9 Dec 2013 16:12:11 -0800
- Subject: [PATCH] Add dynamic linker support for $EXEC_ORIGIN.
- Authentication-results: sourceware.org; auth=none
The $ORIGIN rpath token expands to the directory containing the true copy of the application executable -- which is to say, if the executable is invoked through a symlink, that symlink will be resolved in the $ORIGIN substitution.
In some cases, such as symlink farms backed by cloud storage filesystems where the resolved path encodes effectively-arbitrary storage data, it is much more useful to have a rpath token that points to the non-expanded, as-called version of the executable path. This patch adds the $EXEC_ORIGIN token for this purpose, resolving to the executable path as passed to execve().
One potential security risk of such a token expansion is that, although copying a setuid/setgid executable (to change its $ORIGIN) loses the setuid/setgid bit, creating a symlink does not -- and so a user could inject arbitrary code into a setuid/setgid executable that references $EXEC_ORIGIN by creating an appropriate symlink. In order to slam the door shut on this security risk, this patch simply prohibits the use of $EXEC_ORIGIN in setuid/setgid binaries.
We have been using a slight variation of this patch at Google for about a year, with no issues.
(Note: This is my first attempt at sending a patch with git send-email; please let me know of any problems!)
Thanks,
- Brooks
---
2013-12-08 Brooks Moses <bmoses@google.com>
* elf/dl-support.c (_dl_exec_origin_path): New global variable.
* elf/dl-dst.h (DL_DST_REQUIRED): Include _dl_exec_origin_path in
computing dst_len.
* elf/dl-load.c (_dl_dst_count): Also handle "EXEC_ORIGIN" token.
(_dl_dst_substitute): Likewise.
* elf/rtld.c (get_at_execfn): New static function.
(get_directory): New static function.
(set_exec_origin_path): New static function.
(dl_main): Call set_exec_origin_path.
* sysdeps/generic/ldsodefs.h (rtld_global_ro): Add
_dl_exec_origin_path.
elf/dl-dst.h | 9 +++--
elf/dl-load.c | 10 +++++-
elf/dl-support.c | 3 ++
elf/rtld.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++
sysdeps/generic/ldsodefs.h | 3 ++
5 files changed, 110 insertions(+), 3 deletions(-)
diff --git a/elf/dl-dst.h b/elf/dl-dst.h
index 3ed95d0..c656380 100644
--- a/elf/dl-dst.h
+++ b/elf/dl-dst.h
@@ -65,8 +65,13 @@
else \
dst_len = (l)->l_origin == (char *) -1 \
? 0 : strlen ((l)->l_origin); \
- dst_len = MAX (MAX (dst_len, GLRO(dl_platformlen)), \
- strlen (DL_DST_LIB)); \
+ \
+ char *exec_origin = GLRO(dl_exec_origin_path); \
+ size_t exec_origin_len = \
+ (exec_origin == NULL) ? 0 : strlen (exec_origin); \
+ \
+ dst_len = MAX (MAX (MAX (dst_len, GLRO(dl_platformlen)), \
+ strlen (DL_DST_LIB)), exec_origin_len); \
if (dst_len > 4) \
__len += __cnt * (dst_len - 4); \
} \
diff --git a/elf/dl-load.c b/elf/dl-load.c
index d3e1cf8..04956dc 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -306,7 +306,8 @@ _dl_dst_count (const char *name, int is_path)
if ((len = is_dst (start, name, "ORIGIN", is_path,
INTUSE(__libc_enable_secure))) != 0
|| (len = is_dst (start, name, "PLATFORM", is_path, 0)) != 0
- || (len = is_dst (start, name, "LIB", is_path, 0)) != 0)
+ || (len = is_dst (start, name, "LIB", is_path, 0)) != 0
+ || (len = is_dst (start, name, "EXEC_ORIGIN", is_path, 0)) != 0)
++cnt;
name = strchr (name + len, '$');
@@ -350,6 +351,13 @@ _dl_dst_substitute (struct link_map *l, const char *name, char *result,
repl = GLRO(dl_platform);
else if ((len = is_dst (start, name, "LIB", is_path, 0)) != 0)
repl = DL_DST_LIB;
+ else if ((len = is_dst (start, name, "EXEC_ORIGIN", is_path, 0)) != 0)
+ {
+ if (INTUSE(__libc_enable_secure) != 0)
+ _dl_fatal_printf ("$EXEC_ORIGIN rpath entry not allowed in setuid/setgid executables.\n");
+
+ repl = GLRO(dl_exec_origin_path);
+ }
if (repl != NULL && repl != (const char *) -1)
{
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 2023bd0..d2d427a 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -67,6 +67,9 @@ void *__libc_stack_end;
/* Path where the binary is found. */
const char *_dl_origin_path;
+/* Directory where the AT_EXECFN is found. */
+const char *_dl_exec_origin_path;
+
/* Nonzero if runtime lookup should not update the .got/.plt. */
int _dl_bind_not;
diff --git a/elf/rtld.c b/elf/rtld.c
index 3d207a3..ec5952b 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -51,6 +51,12 @@ extern __typeof (__mempcpy) __mempcpy attribute_hidden;
extern __typeof (_exit) exit_internal asm ("_exit") attribute_hidden;
#define _exit exit_internal
+/* Iterate over auxv, find AT_EXECFN if any. */
+static char * get_at_execfn(ElfW(auxv_t) *auxv);
+
+/* Given file path, return absolute directory path. */
+static char * get_directory (const char *file_path);
+
/* Helper function to handle errors while resolving symbols. */
static void print_unresolved (int errcode, const char *objname,
const char *errsting);
@@ -73,6 +79,9 @@ enum mode { normal, list, verify, trace };
all the entries. */
static void process_envvars (enum mode *modep);
+/* Set GLRO(dl_exec_origin_path). */
+static void set_exec_origin_path(const char *exe_path);
+
#ifdef DL_ARGV_NOT_RELRO
int _dl_argc attribute_hidden;
char **_dl_argv = NULL;
@@ -1032,6 +1041,8 @@ of this helper program; chances are you did not intend to run this program.\n\
in LIST\n\
--audit LIST use objects named in LIST as auditors\n");
+ set_exec_origin_path (INTUSE(_dl_argv)[1]);
+
++_dl_skip_args;
--_dl_argc;
++INTUSE(_dl_argv);
@@ -1126,6 +1137,8 @@ of this helper program; chances are you did not intend to run this program.\n\
}
else
{
+ set_exec_origin_path (get_at_execfn (auxv));
+
/* Create a link_map for the executable itself.
This will be what dlopen on "" returns. */
main_map = _dl_new_object ((char *) "", "", lt_executable, NULL,
@@ -2807,3 +2820,78 @@ print_statistics (hp_timing_t *rtld_total_timep)
}
#endif
}
+
+/* Given file path, return an absolute directory path.
+ Examples: in: "/foo/bar/a.out", out: "/foo/bar/";
+ in: "./a.out", out: "/dot/resolved/to/full/path/./". */
+static char *
+get_directory (const char *file_path)
+{
+ assert (file_path != NULL);
+
+ /* Find the end of the directory substring in file_path. */
+ size_t path_len = strlen (file_path);
+ while (path_len > 0 && file_path[path_len - 1] != '/')
+ --path_len;
+
+ /* Allocate space and set the path prefix according to whether or not
+ this is an absolute path. */
+ char *dest;
+ char *full_dir_path;
+ if (file_path[0] == '/')
+ {
+ full_dir_path = malloc (path_len + 1);
+ assert (full_dir_path != NULL);
+ dest = full_dir_path;
+ }
+ else
+ {
+ /* For a relative path, we need to include space for the largest
+ possible current path, a joining '/', the relevant part of
+ file_path, and a trailing '\0'. */
+ full_dir_path = malloc (PATH_MAX + path_len + 2);
+ assert (full_dir_path != NULL);
+
+ char *status = __getcwd (full_dir_path, PATH_MAX);
+ assert (status != NULL);
+
+ char *dest = __rawmemchr (full_dir_path, '\0');
+ if (dest[-1] != '/')
+ *dest++ = '/';
+ }
+
+ if (path_len > 0)
+ dest = __mempcpy (dest, file_path, path_len);
+ *dest = '\0';
+
+ /* Confirm that the constructed path is valid. */
+ struct stat64 st;
+ assert (__xstat64 (_STAT_VER, full_dir_path, &st) == 0);
+
+ return full_dir_path;
+}
+
+/* Set GLRO(dl_exec_origin_path). */
+static void
+set_exec_origin_path (const char *exe_path)
+{
+ assert (GLRO(dl_exec_origin_path) == NULL);
+
+ if (GLRO(dl_origin_path) != NULL)
+ GLRO(dl_exec_origin_path) = strdup (GLRO(dl_origin_path));
+ else if (exe_path != NULL)
+ GLRO(dl_exec_origin_path) = get_directory (exe_path);
+}
+
+/* Iterate over auxv, find AT_EXECFN if any. */
+static char *
+get_at_execfn (ElfW(auxv_t) *auxv)
+{
+ assert (auxv != NULL);
+
+ for (; auxv->a_type != AT_NULL; ++auxv)
+ if (auxv->a_type == AT_EXECFN)
+ return (char *) auxv->a_un.a_val;
+
+ return NULL;
+}
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 146aca4..561a763 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -518,6 +518,9 @@ struct rtld_global_ro
/* Location of the binary. */
EXTERN const char *_dl_origin_path;
+ /* Directory where the AT_EXECFN is found. */
+ EXTERN const char *_dl_exec_origin_path;
+
/* -1 if the dynamic linker should honor library load bias,
0 if not, -2 use the default (honor biases for normal
binaries, don't honor for PIEs). */
--
1.8.5.1