From 2d028cbedbf2ed783d31f1873312ad0b9fac4047 Mon Sep 17 00:00:00 2001 From: Ulrich Drepper Date: Sat, 7 May 2011 16:44:26 +0100 Subject: Allow $ORIGIN to reference trusted directoreis in SUID binaries. (cherry picked from commit 47c3cd7a74e8c089d60d603afce6d9cf661178d6) --- ChangeLog | 10 +++++ elf/dl-load.c | 124 +++++++++++++++++++++++++++++++++++++++++++--------------- 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 + Ulrich Drepper + + [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 * 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) -- cgit 1.4.1