2011-05-11 Ulrich Drepper [BZ #12393] * elf/dl-load.c (is_trusted_path): Remove unnecessary test. (is_trusted_path_normalize): Skip initial colon. Append slash to empty buffer. Duplicate is_trusted_path code but allow constructed patch to be prefix. (is_dst): Allow $ORIGIN followed by /. (_dl_dst_substitute): Correct clearing of check_for_trusted. Correct testing of result of is_trusted_path_normalize (decompose_rpath): Fix warning. 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_trusted_path_normalize): Wrapper for /../ and /./ normalization. (_dl_dst_substitute): Verify expanded $ORIGIN path elements using is_trusted_path_normalize() in setuid scripts. 2011-03-14 Andreas Schwab * elf/dl-load.c (_dl_dst_substitute): When skipping the first rpath element also skip the following colon. (expand_dynamic_string_token): Add is_path parameter and pass down to DL_DST_REQUIRED and _dl_dst_substitute. (decompose_rpath): Call expand_dynamic_string_token with non-zero is_path. Ignore empty rpaths. (_dl_map_object_from_fd): Call expand_dynamic_string_token with zero is_path. 2011-03-06 Ulrich Drepper * elf/dl-load.c (_dl_map_object): If we are looking for the first to-be-loaded object along a path to loader is ld.so. --- glibc-2.13/elf/dl-load.c 2011-05-20 21:53:43.766426054 +0200 +++ glibc-2.14/elf/dl-load.c 2011-05-31 09:59:16.781617374 +0200 @@ -1,5 +1,5 @@ /* Map in a shared object's segments from the file. - Copyright (C) 1995-2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc. + Copyright (C) 1995-2007, 2009, 2010, 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,87 @@ local_strdup (const char *s) } +static bool +is_trusted_path (const char *path, size_t len) +{ + 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) +{ + if (len == 0) + return false; + + if (*path == ':') + { + ++path; + --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++ = '/'; + + const char *trun = system_dirs; + + for (size_t idx = 0; idx < nsystem_dirs_len; ++idx) + { + if (wnp - npath >= system_dirs_len[idx] + && memcmp (trun, npath, system_dirs_len[idx]) == 0) + /* Found it. */ + return true; + + trun += system_dirs_len[idx] + 1; + } + + return false; +} + + static size_t is_dst (const char *start, const char *name, const char *str, int is_path, int secure) @@ -200,7 +281,8 @@ is_dst (const char *start, const char *n return 0; if (__builtin_expect (secure, 0) - && ((name[len] != '\0' && (!is_path || name[len] != ':')) + && ((name[len] != '\0' && name[len] != '/' + && (!is_path || name[len] != ':')) || (name != start + 1 && (!is_path || name[-2] != ':')))) return 0; @@ -240,13 +322,14 @@ _dl_dst_substitute (struct link_map *l, 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 +348,9 @@ _dl_dst_substitute (struct link_map *l, 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); @@ -284,6 +370,10 @@ _dl_dst_substitute (struct link_map *l, name += len; while (*name != '\0' && (!is_path || *name != ':')) ++name; + /* Also skip following colon if this is the first rpath + element, but keep an empty element at the end. */ + if (wp == result && is_path && *name == ':' && name[1] != '\0') + ++name; } else /* No DST we recognize. */ @@ -293,11 +383,28 @@ _dl_dst_substitute (struct link_map *l, { *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; + else + last_elem = wp; + + check_for_trusted = false; + } } } 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; @@ -310,7 +417,7 @@ _dl_dst_substitute (struct link_map *l, belonging to the map is loaded. In this case the path element containing $ORIGIN is left out. */ static char * -expand_dynamic_string_token (struct link_map *l, const char *s) +expand_dynamic_string_token (struct link_map *l, const char *s, int is_path) { /* We make two runs over the string. First we determine how large the resulting string is and then we copy it over. Since this is no @@ -321,7 +428,7 @@ expand_dynamic_string_token (struct link char *result; /* Determine the number of DST elements. */ - cnt = DL_DST_COUNT (s, 1); + cnt = DL_DST_COUNT (s, is_path); /* If we do not have to replace anything simply copy the string. */ if (__builtin_expect (cnt, 0) == 0) @@ -335,7 +442,7 @@ expand_dynamic_string_token (struct link if (result == NULL) return NULL; - return _dl_dst_substitute (l, s, result, 1); + return _dl_dst_substitute (l, s, result, is_path); } @@ -407,33 +514,8 @@ fillin_rpath (char *rpath, struct r_sear 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) @@ -551,13 +633,21 @@ decompose_rpath (struct r_search_path_st /* Make a writable copy. At the same time expand possible dynamic string tokens. */ - copy = expand_dynamic_string_token (l, rpath); + copy = expand_dynamic_string_token (l, rpath, 1); if (copy == NULL) { errstring = N_("cannot create RUNPATH/RPATH copy"); goto signal_error; } + /* Ignore empty rpaths. */ + if (*copy == 0) + { + free (copy); + sps->dirs = (struct r_search_path_elem **) -1; + return false; + } + /* Count the number of necessary elements in the result array. */ nelems = 0; for (cp = copy; *cp != '\0'; ++cp) @@ -2109,7 +2201,9 @@ _dl_map_object (struct link_map *loader, { #ifdef SHARED // XXX Correct to unconditionally default to namespace 0? - l = loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded; + l = (loader + ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded + ?: &GL(dl_rtld_map)); #else l = loader; #endif @@ -2175,7 +2269,7 @@ _dl_map_object (struct link_map *loader, { /* The path may contain dynamic string tokens. */ realname = (loader - ? expand_dynamic_string_token (loader, name) + ? expand_dynamic_string_token (loader, name, 0) : local_strdup (name)); if (realname == NULL) fd = -1;