? autom4te.cache
? config.guess
? gnulib
? stamp-h1
? lib/.deps
? m4/Makefile
? po/.pot
? po/stamp-po
? po/tmp
Index: configure.in
===================================================================
RCS file: /cvsroot/findutils/findutils/configure.in,v
retrieving revision 1.66
diff -u -r1.66 configure.in
--- configure.in	8 Feb 2005 00:00:17 -0000	1.66
+++ configure.in	12 Feb 2005 21:08:24 -0000
@@ -8,7 +8,7 @@
 dnl Set of available languages.
 ALL_LINGUAS="be ca da de el eo es et fi fr ga gl hr hu id it ja ko lg ms nl pl pt_BR ro ru sk sl sr sv tr zh_CN"
 
-AM_INIT_AUTOMAKE(findutils, 4.2.17)
+AM_INIT_AUTOMAKE(findutils, 4.2.18-CVS)
 
 AC_SUBST(INCLUDES)dnl
 AC_ARG_ENABLE(id-cache,
@@ -83,6 +83,8 @@
 AC_CHECK_HEADERS(fcntl.h string.h limits.h unistd.h errno.h stdlib.h stddef.h)
 AC_CHECK_HEADERS(unistd.h sys/types.h inttypes.h fcntl.h locale.h stdint.h)
 AC_CHECK_HEADERS(sys/param.h mntent.h sys/mnttab.h sys/mntio.h sys/mkdev.h)
+dnl find.c needs sys/utsname.h because it calls uname(2).
+AC_CHECK_HEADERS(sys/utsname.h)
 AC_HEADER_MAJOR
 AC_HEADER_DIRENT
 AC_HEADER_STAT
Index: doc/find.texi
===================================================================
RCS file: /cvsroot/findutils/findutils/doc/find.texi,v
retrieving revision 1.72
diff -u -r1.72 find.texi
--- doc/find.texi	10 Feb 2005 22:26:48 -0000	1.72
+++ doc/find.texi	12 Feb 2005 21:08:26 -0000
@@ -3045,48 +3045,96 @@
 
 @subsection Changing the Current Working Directory
 As find searches the file system, it finds subdirectories and then
-searches within them by changing its working directory.    First,
+searches within them by changing its working directory.  First,
 @code{find} notices a subdirectory.  It then decides if that
 subdirectory meets the criteria for being searched; that is, any
 @samp{-xdev} or @samp{-prune} expressions are taken into account.  The
-@code{find} program will then change working directory using the
-@code{chdir()} system call and proceed to search the directory.  
+@code{find} program will then change working directory and proceed to
+search the directory.
 
 A race condition attack might take the form that once the checks
 relevant to @samp{-xdev} and @samp{-prune} have been done, an attacker
 might rename the directory that was being considered, and put in its
-place a symbolic link that actually points somewhere else.  The
-@code{find} command would then issue the @code{chdir()} system call,
-changing its working directory to the destination of the symbolic link
-(@code{chdir()} will always dereference symbolic links).  This
-manipulation leaves @code{find} with a working directory chosen by an
-attacker, bypassing any protection apparently provided by @samp{-xdev}
-and @samp{-prune}, and any protection provided by being able to
-@emph{not} list particular directories on the @code{find} command
-line.  This form of attack is particularly problematic if the attacker
-can predict when the @code{find} command will be run, as is the case
-with @code{cron} tasks for example.  
+place a symbolic link that actually points somewhere else.  
+
+The idea behind this attack is to fool @code{find} into going into the
+wrong directory.  This would leave @code{find} with a working
+directory chosen by an attacker, bypassing any protection apparently
+provided by @samp{-xdev} and @samp{-prune}, and any protection
+provided by being able to @emph{not} list particular directories on
+the @code{find} command line.  This form of attack is particularly
+problematic if the attacker can predict when the @code{find} command
+will be run, as is the case with @code{cron} tasks for example.
 
 GNU @code{find} has specific safeguards to prevent this general class
-of problem.  Each time @code{find} changes directory, it examines the
-directory it is about to move to, issues the @code{chdir()} system
-call, and then checks that it has ended up in the subdirectory it
-expected.  If not, an error message is issued and @code{find} exits
-immediately.  This prevents filesystem manipulation attacks from
-persuading @code{find} to search parts of the filesystem it did not
-intend.
+of problem.  The exact form of these safeguards depends on the
+properties of your system.  
+
+@subsubsection O_FOLLOW 
+
+If your system supports the O_NOFOLLOW flag @footnote{GNU/Linux
+(kernel version 2.1.126 and later) and FreeBSD (3.0-CURRENT and later)
+support this} to the @code{open(2)} system call, @code{find} uses it
+when safely changing directory.  The target directory is first opened
+and then @code{find} changes working directory with the
+@code{fchdir()} system call.  This ensures that symbolic links are not
+followed, preventing the sort of race condition attack in which use
+is made of symbolic links.
+
+If for any reason this approach does not work, @code{find} will fall
+back on the method which is normally used if O_NOFOLLOW is not
+supported.
+
+You can tell if your system supports O_NOFOLLOW by running 
+
+@example
+find --version
+@end example
+
+This will tell you the version number and which features are enabled.
+For example, if I run this on my system now, this gives:
+@example
+GNU find version 4.2.18-CVS
+Features enabled: D_TYPE O_NOFOLLOW(enabled)
+@end example
+
+Here, you can see that I am running a version of find which was built
+from the development (CVS) code prior to the release of
+findutils-4.2.18, and that the D_TYPE and O_NOFOLLOW features are
+present.  O_NOFOLLOW is qualified with ``enabled''.  This simply means
+that the current system seems to support O_NOFOLLOW.  This check is
+needed because it is possible to build find on a system that defines
+O_NOFOLLOW and then run it on a system that ignores the O_NOFOLLOW
+flag.  We try to detect such cases at startup by checking the
+operating system and version number; when this happens you will see
+``O_NOFOLLOW(disabled)'' instead.
+
+@subsubsection Systems without O_NOFOLLOW
+
+The strategy for preventing this type of problem on systems that lack
+support for the O_NOFOLLOW flag is more complex.  Each time
+@code{find} changes directory, it examines the directory it is about
+to move to, issues the @code{chdir()} system call, and then checks
+that it has ended up in the subdirectory it expected.  If not, an
+error message is issued and @code{find} exits immediately.  This
+method also prevents filesystem manipulation attacks from persuading
+@code{find} to search parts of the filesystem it did not intend.
+However, we heve to take special steps in order not to be
+unneccessarily concluding that there is a problem aut ``automount''
+mount points.
 
 @subsubsection Working with automounters
 
-Where an automounter is in use, it can be the case that the use of the
-@code{chdir()} system call can itself cause a new filesystem to be
-mounted at that point.  This will cause @code{find}'s security check
-to fail.  However, this does not normally represent a security problem
-(since the automounter configuration is normally set up by the system
-administrator).  Therefore, if the @code{chdir()} sanity check fails,
-@code{find} will check to see if a new filesystem has been mounted at
-the current directory; if so, @code{find} will issue a warning message
-and continue.  
+Where an automounter is in use on systems that do not support
+O_FOLLOW, it can be the case that the use of the @code{chdir()} system
+call can itself cause a new filesystem to be mounted at that point.
+This will cause @code{find}'s security check to fail.  However, this
+does not normally represent a security problem (since the automounter
+configuration is normally set up by the system administrator).
+Therefore, if the @code{chdir()} sanity check fails, @code{find} will
+check to see if a new filesystem has been mounted at the current
+directory; if so, @code{find} will issue a warning message and
+continue.
 
 To make this solution work, @code{find} reads the list of mounted
 filesystems at startup, and again when the sanity check fails.  It
@@ -3103,24 +3151,13 @@
 it does this even if the affected mount point is not within the
 directory tree that find would have searched anyway.
 
-This is very unfortunate.  However, as far as I can see the
-alternatives are also nasty:
-
-@itemize @bullet
-@item
-leave @code{find} vulnerable to race conditions (i.e. not detect a
-change in a directory as we @code{chdir()} into it)
-@item
-use the sanity check, but don't take account of automounters - this
-results in find not being able to search large parts of the system if
-an automounter is in place. 
-@end itemize
-
-So, the residual problem you are left with is that your system will
-not work well if it is a client of a dead NFS server.  As far as I can
-tell, it is not possible to fix all three problems at once.  If you
-have some ideas about how @code{find} could do this better, please
-send email to the @email{bug-findutils@@gnu.org} mailing list.
+This is very unfortunate.  However, this problem only affects systems
+that have no support for O_FOLLOW.  As far as I can tell, it is not
+possible on such systems to fix all three problems (the race
+condition, the false-alarm at automount mount points, and the hang at
+startup if there is a dead NFS server) at once.  If you have some
+ideas about how @code{find} could do this better, please send email to
+the @email{bug-findutils@@gnu.org} mailing list.
 
 @subsection Race Conditions with -exec
 
@@ -3167,6 +3204,14 @@
 be processed is passed to the invoked command, with a @samp{./}
 prepended (giving @file{./passwd} in our example).
 
+The @samp{-execdir} action refuses to do anything if the current
+directory is included in the @var{$PATH} environment variable.  This
+is necessary because @samp{-execdir} runs programs in the same
+directory in which it finds files -- in general, such a directory
+might be writable by untrusted users.  For similar reasons,
+@samp{-execdir} does not allow @samp{@{@}} to appear in the name of
+the command to be run.
+
 @subsection Race Conditions with -print and -print0
 
 The @samp{-print} and @samp{-print0} actions can be used to produce a
Index: find/defs.h
===================================================================
RCS file: /cvsroot/findutils/findutils/find/defs.h,v
retrieving revision 1.35
diff -u -r1.35 defs.h
--- find/defs.h	24 Jan 2005 16:50:30 -0000	1.35
+++ find/defs.h	12 Feb 2005 21:08:26 -0000
@@ -518,6 +518,12 @@
   
   /* Pointer to the function used to stat files. */
   int (*xstat) (const char *name, struct stat *statbuf);
+
+
+  /* Indicate if we can implement safely_chdir() using the O_NOFOLLOW 
+   * flag to open(2). 
+   */
+  boolean open_nofollow_available;
 };
 extern struct options options;
 
Index: find/find.c
===================================================================
RCS file: /cvsroot/findutils/findutils/find/find.c,v
retrieving revision 1.65
diff -u -r1.65 find.c
--- find/find.c	7 Feb 2005 00:41:15 -0000	1.65
+++ find/find.c	12 Feb 2005 21:08:27 -0000
@@ -38,6 +38,10 @@
 #include <sys/file.h>
 #endif
 
+#ifdef HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+
 #include "../gnulib/lib/xalloc.h"
 #include "../gnulib/lib/human.h"
 #include "../gnulib/lib/canonicalize.h"
@@ -335,6 +339,37 @@
   return 0;
 }
 
+/* Determine if we can use O_NOFOLLOW.
+ */
+#if defined(O_NOFOLLOW)
+static boolean 
+check_nofollow(void)
+{
+  struct utsname uts;
+  float  release;
+
+  if (0 == uname(&uts))
+    {
+      /* POSIX requires that atof() ignore "unrecognised suffixes". */
+      release = atof(uts.release);
+      
+      if (0 == strcmp("Linux", uts.sysname))
+	{
+	  /* Linux kernels 2.1.126 and earlier ignore the O_NOFOLLOW flag. */
+	  return release >= 2.2; /* close enough */
+	}
+      else if (0 == strcmp("FreeBSD", uts.sysname)) 
+	{
+	  /* FreeBSD 3.0-CURRENT and later support it */
+	  return release >= 3.1;
+	}
+    }
+
+  /* Well, O_NOFOLLOW was defined, so we'll try to use it. */
+  return true;
+}
+#endif
+
 int
 main (int argc, char **argv)
 {
@@ -383,6 +418,12 @@
     options.output_block_size = 512;
   else
     options.output_block_size = 1024;
+
+#ifdef O_NOFOLLOW
+  options.open_nofollow_available = check_nofollow();
+#else
+  options.open_nofollow_available = false;
+#endif
   
   if (getenv("FIND_BLOCK_SIZE"))
     {
@@ -392,8 +433,21 @@
   options.no_leaf_check = false;
   set_follow_state(SYMLINK_NEVER_DEREF); /* The default is equivalent to -P. */
 
-  init_mounted_dev_list();
-
+  /* safely_chdir() needs to check that it has ended up in the right place. 
+   * To avoid bailing out when something gets automounted, it checks if 
+   * the target directory appears to have had a directory mounted on it as
+   * we chdir()ed.  The problem with this is that in order to notice that 
+   * a filesystem was mounted, we would need to lstat() all the mount points.
+   * That strategy loses if our machine is a client of a dead NFS server.
+   *
+   * Hence if safely_chdir() and wd_sanity_check() can manage without needing 
+   * to know the mounted device list, we do that.  
+   */
+  if (!options.open_nofollow_available)
+    {
+      init_mounted_dev_list();
+    }
+  
 #ifdef DEBUG
   fprintf (stderr, "cur_day_start = %s", ctime (&options.cur_day_start));
 #endif /* DEBUG */
@@ -858,10 +912,10 @@
  *
  */
 static enum SafeChdirStatus
-safely_chdir(const char *dest,
-	     enum TraversalDirection direction,
-	     struct stat *statbuf_dest,
-	     enum ChdirSymlinkHandling symlink_handling)
+safely_chdir_lstat(const char *dest,
+		   enum TraversalDirection direction,
+		   struct stat *statbuf_dest,
+		   enum ChdirSymlinkHandling symlink_handling)
 {
   struct stat statbuf_arrived;
   int rv, dotfd=-1;
@@ -878,6 +932,10 @@
   
 
   saved_errno = errno = 0;
+  time(NULL);
+  opendir(".");
+  time(NULL);
+
   dotfd = open(".", O_RDONLY);
   if (dotfd >= 0)
     {
@@ -1035,6 +1093,63 @@
   return rv;
 }
 
+
+static enum SafeChdirStatus
+safely_chdir_nofollow(const char *dest,
+		      enum TraversalDirection direction,
+		      struct stat *statbuf_dest,
+		      enum ChdirSymlinkHandling symlink_handling)
+{
+#ifdef O_NOFOLLOW
+  int extraflags = following_links() ? 0 : O_NOFOLLOW;
+#else
+  int extraflags = 0;
+#endif
+
+  int fd = open(dest, O_RDONLY|extraflags);
+  if (fd < 0)
+    {
+      /* POSIX normally requires this to fail with EISDIR, but
+       * it's worth a try.
+       */
+      fd = open(dest, O_WRONLY|extraflags);
+      if (fd < 0)
+	goto fallback;
+    }
+
+  if (0 == fchdir(fd))
+    {
+      close(fd);
+      return SafeChdirOK;
+    }
+  else
+    {
+      close(fd);
+      goto fallback;
+    }
+  
+ fallback:  
+  if (NULL == mounted_devices)
+    init_mounted_dev_list();
+  
+  return safely_chdir_lstat(dest, direction, statbuf_dest, symlink_handling);
+}
+
+static enum SafeChdirStatus
+safely_chdir(const char *dest,
+	     enum TraversalDirection direction,
+	     struct stat *statbuf_dest,
+	     enum ChdirSymlinkHandling symlink_handling)
+{
+#if defined O_NOFOLLOW  
+  if (options.open_nofollow_available)
+    return safely_chdir_nofollow(dest, direction, statbuf_dest, symlink_handling);
+#endif
+  
+  return safely_chdir_lstat(dest, direction, statbuf_dest, symlink_handling);
+}
+
+
 
 /* Safely go back to the starting directory. */
 static void
@@ -1048,6 +1163,13 @@
 #ifdef DEBUG_STAT
       fprintf(stderr, "chdir_back(): chdir(\"%s\")\n", starting_dir);
 #endif
+      
+      /* We will need the mounted device list.  Get it now if we don't
+       * already have it.
+       */
+      if (NULL == mounted_devices)
+	init_mounted_dev_list();
+      
       if (chdir (starting_dir) != 0)
 	error (1, errno, "%s", starting_dir);
 
@@ -1120,8 +1242,9 @@
       chdir_status = safely_chdir(parent_dir, direction, &st, SymlinkFollowOk);
       if (SafeChdirOK != chdir_status)
 	{
+	  const char *what = (SafeChdirFailWouldBeUnableToReturn == chdir_status) ? "." : parent_dir;
 	  if (errno)
-	    error (0, errno, "%s", parent_dir);
+	    error (0, errno, "%s", what);
 	  else
 	    error (0, 0, "Failed to safely change directory into `%s'",
 		   parent_dir);
@@ -1483,7 +1606,6 @@
        */
       complete_pending_execdirs(eval_tree);
       
-#if USE_SAFE_CHDIR
       if (strcmp (name, "."))
 	{
 	  enum SafeChdirStatus status = safely_chdir (name, TraversingDown, &stat_buf, SymlinkHandleDefault);
@@ -1500,9 +1622,13 @@
 	      dir_ids[dir_curr].ino = stat_buf.st_ino;
 	      break;
       
+	    case SafeChdirFailWouldBeUnableToReturn:
+	      error (0, errno, ".");
+	      state.exit_status = 1;
+	      break;
+	      
 	    case SafeChdirFailNonexistent:
 	    case SafeChdirFailStat:
-	    case SafeChdirFailWouldBeUnableToReturn:
 	    case SafeChdirFailSymlink:
 	    case SafeChdirFailNotDir:
 	    case SafeChdirFailChdirFailed:
@@ -1511,46 +1637,7 @@
 	      return;
 	    }
 	}
-#else
-      if (0 != strcmp (name, "."))
-	{
-#ifdef DEBUG_STAT
-	  fprintf(stderr, "process_dir(): chdir(\"%s\")\n", name);
-#endif
-	  if (chdir (name) < 0)
-	    {
-	      error (0, errno, "%s", pathname);
-	      exit_status = 1;
-	      return;
-	    }
-	}
-      
 
-      /* Check that we are where we should be. */
-      if (1)
-	{
-	  boolean changed = false;
-	  wd_sanity_check(pathname,
-			  program_name,
-			  ".",
-			  dir_ids[dir_curr].dev,
-			  dir_ids[dir_curr].ino,
-			  &stat_buf, 0, __LINE__, 
-			  TraversingDown,
-			  FATAL_IF_SANITY_CHECK_FAILS,
-			  &changed);
-	  if (changed)
-	    {
-	      /* If there had been a change but wd_sanity_check()
-	       * accepted it, we need to accept that on the 
-	       * way back up as well, so modify our record 
-	       * of what we think we should see later. 
-	       */
-	      dir_ids[dir_curr].dev = stat_buf.st_dev;
-	      dir_ids[dir_curr].ino = stat_buf.st_ino;
-	    }
-	}
-#endif
 
       for (idx=0, namep = name_space; *namep; namep += file_len - pathname_len + 1, ++idx)
 	{
@@ -1625,7 +1712,7 @@
        */
       complete_pending_execdirs(eval_tree); 
 
-#if USE_SAFE_CHDIR
+
       if (strcmp (name, "."))
 	{
 	  enum SafeChdirStatus status;
@@ -1651,9 +1738,12 @@
 	    case SafeChdirOK:
 	      break;
       
+	    case SafeChdirFailWouldBeUnableToReturn:
+	      error (1, errno, ".");
+	      return;
+	      
 	    case SafeChdirFailNonexistent:
 	    case SafeChdirFailStat:
-	    case SafeChdirFailWouldBeUnableToReturn:
 	    case SafeChdirFailSymlink:
 	    case SafeChdirFailNotDir:
 	    case SafeChdirFailChdirFailed:
@@ -1671,74 +1761,7 @@
 	      did.dev = starting_stat_buf.st_dev;
 	      did.ino = starting_stat_buf.st_ino;
 	    }
-
-	  wd_sanity_check(pathname,
-			  program_name,
-			  parent,
-			  did.dev,
-			  did.ino,
-			  &stat_buf,
-			  deref ? 1 : 0,
-			  __LINE__, 
-			  TraversingUp,
-			  FATAL_IF_SANITY_CHECK_FAILS,
-			  &changed);
-	}
-#else
-      if (strcmp (name, "."))
-	{
-	  /* We could go back and do the next command-line arg
-	     instead, maybe using longjmp.  */
-	  char const *dir;
-	  boolean deref = following_links() ? true : false;
-	  
-	  if (!deref)
-	    dir = "..";
-	  else
-	    {
-	      chdir_back ();
-	      dir = parent;
-	    }
-
-#ifdef DEBUG_STAT
-	  fprintf(stderr, "process_dir(): chdir(\"%s\")\n", dir);
-#endif
-	  if (chdir (dir) != 0)
-	    error (1, errno, "%s", parent);
-
-	  /* Check that we are where we should be. */
-	  if (1)
-	    {
-	      boolean changed = false;
-	      struct stat tmp;
-	      int problem_is_with_parent;
-	      
-	      memset(&tmp, 0, sizeof(tmp));
-	      if (dir_curr > 0)
-		{
-		  tmp.st_dev = dir_ids[dir_curr-1].dev;
-		  tmp.st_ino = dir_ids[dir_curr-1].ino;
-		}
-	      else
-		{
-		  tmp.st_dev = starting_stat_buf.st_dev;
-		  tmp.st_ino = starting_stat_buf.st_ino;
-		}
-
-	      problem_is_with_parent = deref ? 1 : 0;
-	      wd_sanity_check(pathname,
-			      program_name,
-			      parent,
-			      tmp.st_dev,
-			      tmp.st_ino,
-			      &stat_buf,
-			      problem_is_with_parent, __LINE__, 
-			      TraversingUp,
-			      FATAL_IF_SANITY_CHECK_FAILS,
-			      &changed);
-	    }
 	}
-#endif
 
       if (cur_path)
 	free (cur_path);
Index: find/parser.c
===================================================================
RCS file: /cvsroot/findutils/findutils/find/parser.c,v
retrieving revision 1.59
diff -u -r1.59 parser.c
--- find/parser.c	6 Feb 2005 09:24:04 -0000	1.59
+++ find/parser.c	12 Feb 2005 21:08:27 -0000
@@ -30,6 +30,11 @@
 #include "buildcmd.h"
 #include "nextelem.h"
 
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#include <sys/file.h>
+#endif
 
 #if ENABLE_NLS
 # include <libintl.h>
@@ -1543,6 +1548,12 @@
   printf("D_TYPE ");
   ++features;
 #endif
+#if defined(O_NOFOLLOW)
+  printf("O_NOFOLLOW(%s) ",
+	 (options.open_nofollow_available ? "enabled" : "disabled"));
+  ++features;
+#endif
+  
   if (0 == features)
     {
       /* For the moment, leave this as English in case someone wants
