aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlrich Drepper <drepper@gmail.com>2011-05-07 16:44:26 +0100
committerPetr Baudis <pasky@ucw.cz>2011-05-27 01:20:13 +0200
commit2d028cbedbf2ed783d31f1873312ad0b9fac4047 (patch)
tree91512867b1883b45cca08f110f5fbdbfae91ca76
parent426cfd73719462d0fedd72d78169218509dae574 (diff)
downloadglibc-2d028cbedbf2ed783d31f1873312ad0b9fac4047.tar
glibc-2d028cbedbf2ed783d31f1873312ad0b9fac4047.tar.gz
glibc-2d028cbedbf2ed783d31f1873312ad0b9fac4047.tar.bz2
glibc-2d028cbedbf2ed783d31f1873312ad0b9fac4047.zip
Allow $ORIGIN to reference trusted directoreis in SUID binaries.
(cherry picked from commit 47c3cd7a74e8c089d60d603afce6d9cf661178d6)
-rw-r--r--ChangeLog10
-rw-r--r--elf/dl-load.c124
2 files changed, 103 insertions, 31 deletions
diff --git a/ChangeLog b/ChangeLog
index e3875af7f4..e6529a690d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2011-05-07 Petr Baudis <pasky@suse.cz>
+ Ulrich Drepper <drepper@gmail.com>
+
+ [BZ #12393]
+ * elf/dl-load.c (fillin_rpath): Move trusted path check...
+ (is_trusted_path): ...to here.
+ (is_norm_trusted_path): Add wrapper for /../ and /./ normalization.
+ (_dl_dst_substitute): Verify expanded $ORIGIN path elements
+ using is_norm_trusted_path() in setuid scripts.
+
2011-05-03 Andreas Schwab <schwab@redhat.com>
* elf/ldconfig.c (add_dir): Don't crash on empty path.
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 2993aa9706..1630d71a1e 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1,5 +1,5 @@
/* Map in a shared object's segments from the file.
- Copyright (C) 1995-2005, 2006, 2007, 2009, 2011 Free Software Foundation, Inc.
+ Copyright (C) 1995-2007, 2009, 2011 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
@@ -168,6 +168,71 @@ local_strdup (const char *s)
}
+static bool
+is_trusted_path (const char *path, size_t len)
+{
+ /* All trusted directories must be complete names. */
+ if (path[0] != '/')
+ return false;
+
+ const char *trun = system_dirs;
+
+ for (size_t idx = 0; idx < nsystem_dirs_len; ++idx)
+ {
+ if (len == system_dirs_len[idx] && memcmp (trun, path, len) == 0)
+ /* Found it. */
+ return true;
+
+ trun += system_dirs_len[idx] + 1;
+ }
+
+ return false;
+}
+
+
+static bool
+is_trusted_path_normalize (const char *path, size_t len)
+{
+ char *npath = (char *) alloca (len + 2);
+ char *wnp = npath;
+
+ while (*path != '\0')
+ {
+ if (path[0] == '/')
+ {
+ if (path[1] == '.')
+ {
+ if (path[2] == '.' && (path[3] == '/' || path[3] == '\0'))
+ {
+ while (wnp > npath && *--wnp != '/')
+ ;
+ path += 3;
+ continue;
+ }
+ else if (path[2] == '/' || path[2] == '\0')
+ {
+ path += 2;
+ continue;
+ }
+ }
+
+ if (wnp > npath && wnp[-1] == '/')
+ {
+ ++path;
+ continue;
+ }
+ }
+
+ *wnp++ = *path++;
+ }
+ if (wnp > npath && wnp[-1] != '/')
+ *wnp++ = '/';
+ *wnp = '\0';
+
+ return is_trusted_path (npath, wnp - npath);
+}
+
+
static size_t
is_dst (const char *start, const char *name, const char *str,
int is_path, int secure)
@@ -240,13 +305,14 @@ _dl_dst_substitute (struct link_map *l, const char *name, char *result,
int is_path)
{
const char *const start = name;
- char *last_elem, *wp;
/* Now fill the result path. While copying over the string we keep
track of the start of the last path element. When we come accross
a DST we copy over the value or (if the value is not available)
leave the entire path element out. */
- last_elem = wp = result;
+ char *wp = result;
+ char *last_elem = result;
+ bool check_for_trusted = false;
do
{
@@ -265,6 +331,9 @@ _dl_dst_substitute (struct link_map *l, const char *name, char *result,
else
#endif
repl = l->l_origin;
+
+ check_for_trusted = (INTUSE(__libc_enable_secure)
+ && l->l_type == lt_executable);
}
else if ((len = is_dst (start, name, "PLATFORM", is_path, 0)) != 0)
repl = GLRO(dl_platform);
@@ -297,11 +366,29 @@ _dl_dst_substitute (struct link_map *l, const char *name, char *result,
{
*wp++ = *name++;
if (is_path && *name == ':')
- last_elem = wp;
+ {
+ /* In SUID/SGID programs, after $ORIGIN expansion the
+ normalized path must be rooted in one of the trusted
+ directories. */
+ if (__builtin_expect (check_for_trusted, false)
+ && is_trusted_path_normalize (last_elem, wp - last_elem))
+ {
+ wp = last_elem;
+ check_for_trusted = false;
+ }
+ else
+ last_elem = wp;
+ }
}
}
while (*name != '\0');
+ /* In SUID/SGID programs, after $ORIGIN expansion the normalized
+ path must be rooted in one of the trusted directories. */
+ if (__builtin_expect (check_for_trusted, false)
+ && is_trusted_path_normalize (last_elem, wp - last_elem))
+ wp = last_elem;
+
*wp = '\0';
return result;
@@ -411,33 +498,8 @@ fillin_rpath (char *rpath, struct r_search_path_elem **result, const char *sep,
cp[len++] = '/';
/* Make sure we don't use untrusted directories if we run SUID. */
- if (__builtin_expect (check_trusted, 0))
- {
- const char *trun = system_dirs;
- size_t idx;
- int unsecure = 1;
-
- /* All trusted directories must be complete names. */
- if (cp[0] == '/')
- {
- for (idx = 0; idx < nsystem_dirs_len; ++idx)
- {
- if (len == system_dirs_len[idx]
- && memcmp (trun, cp, len) == 0)
- {
- /* Found it. */
- unsecure = 0;
- break;
- }
-
- trun += system_dirs_len[idx] + 1;
- }
- }
-
- if (unsecure)
- /* Simply drop this directory. */
- continue;
- }
+ if (__builtin_expect (check_trusted, 0) && !is_trusted_path (cp, len))
+ continue;
/* See if this directory is already known. */
for (dirp = GL(dl_all_dirs); dirp != NULL; dirp = dirp->next)