about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog10
-rw-r--r--NEWS5
-rw-r--r--elf/dl-load.c124
3 files changed, 106 insertions, 33 deletions
diff --git a/ChangeLog b/ChangeLog
index d7bb7340d0..e93c9a5643 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-06  Paul Pluzhnikov  <ppluzhnikov@google.com>
 
 	* sysdeps/unix/sysv/linux/sys/sysmacros.h: Add missing
diff --git a/NEWS b/NEWS
index 5d37da2c10..43da517254 100644
--- a/NEWS
+++ b/NEWS
@@ -22,8 +22,9 @@ Version 2.14
 
 * The following bugs are resolved with this release:
 
-  11724, 12420, 12445, 12454, 12460, 12469, 12489, 12509, 12510, 12518, 12583,
-  12587, 12597, 12631, 12650, 12653, 12655, 12685, 12714, 12717, 12723
+  11724, 12393, 12420, 12445, 12454, 12460, 12469, 12489, 12509, 12510,
+  12518, 12583, 12587, 12597, 12631, 12650, 12653, 12655, 12685, 12714,
+  12717, 12723
 
 Version 2.13
 
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 00ea465e13..f2773d5686 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, 2010, 2011 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,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)