bug-fileutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: rm - recursive directory removal race condition


From: Jim Meyering
Subject: Re: rm - recursive directory removal race condition
Date: Sat, 09 Mar 2002 22:04:18 +0100
User-agent: Gnus/5.090006 (Oort Gnus v0.06) Emacs/21.2.50 (i686-pc-linux-gnu)

Thanks for the report!

Here's a patch relative to fileutils-4.1.6

        Don't allow a malicious user to trick another user's rm process into
        removing unintended files.  In one scenario, if root is removing a
        hierarchy that is writable by the malicious user, that user may trick
        root into removing all of `/'.  Reported by Wojciech Purczynski.

        * src/remove.c (remove_dir): After chdir `..', call lstat to get the
        dev/inode of "." and fail if they aren't the same as the old numbers.
        (remove_cwd_entries): New parameter, `cwd_dev_ino'.
        (remove_dir): Likewise.
        (rm): Likewise.
        Adjust all callers.
        * src/mv.c (do_move): The first time we resort to copy/remove,
        call lstat `.' to get the device/inode numbers now required for rm.
        * src/rm.c (main): Call lstat `.' to get the device/inode numbers
        now required for rm.
        * src/remove.h (struct dev_ino): Declare new type.
        (rm): Add a parameter to the prototype.

Index: src/remove.c
===================================================================
RCS file: /fetish/fileutils/src/remove.c,v
retrieving revision 1.42
retrieving revision 1.44
diff -u -p -r1.42 -r1.44
--- src/remove.c        2002/02/01 23:30:23     1.42
+++ src/remove.c        2002/03/09 21:02:05     1.44
@@ -414,10 +414,13 @@ same_file (const char *file_1, const cha
 
 
 /* Recursively remove all of the entries in the current directory.
-   Return an indication of the success of the operation.  */
+   Return an indication of the success of the operation.
+   CWD_DEV_INO must store the device and inode numbers of the
+   current working directory.  */
 
 static enum RM_status
-remove_cwd_entries (const struct rm_options *x)
+remove_cwd_entries (const struct rm_options *x,
+                   struct dev_ino const *cwd_dev_ino)
 {
   /* NOTE: this is static.  */
   static DIR *dirp = NULL;
@@ -530,7 +533,7 @@ remove_cwd_entries (const struct rm_opti
          /* CAUTION: after this call to rm, DP may not be valid --
             it may have been freed due to a close in a recursive call
             (through rm and remove_dir) to this function.  */
-         tmp_status = rm (&fs, 0, x);
+         tmp_status = rm (&fs, 0, x, cwd_dev_ino);
 
          /* Update status.  */
          if (tmp_status > status)
@@ -645,12 +648,14 @@ remove_file (struct File_spec *fs, const
    FIXME: describe need_save_cwd parameter.  */
 
 static enum RM_status
-remove_dir (struct File_spec *fs, int need_save_cwd, const struct rm_options 
*x)
+remove_dir (struct File_spec *fs, int need_save_cwd,
+           struct rm_options const *x, struct dev_ino const *cwd_dev_ino)
 {
   enum RM_status status;
   struct saved_cwd cwd;
   char *dir_name = fs->filename;
   const char *fmt = NULL;
+  struct dev_ino tmp_cwd_dev_ino;
 
   if (!x->recursive)
     {
@@ -719,6 +724,9 @@ was replaced with either another directo
               (unsigned long)(sb.st_dev),
               (unsigned long)(sb.st_ino));
       }
+
+    tmp_cwd_dev_ino.st_dev = sb.st_dev;
+    tmp_cwd_dev_ino.st_ino = sb.st_ino;
   }
 
   push_dir (dir_name);
@@ -728,7 +736,7 @@ was replaced with either another directo
      remove_cwd_entries may close the directory.  */
   ASSIGN_STRDUPA (dir_name, dir_name);
 
-  status = remove_cwd_entries (x);
+  status = remove_cwd_entries (x, &tmp_cwd_dev_ino);
 
   pop_dir ();
 
@@ -742,11 +750,34 @@ was replaced with either another directo
        }
       free_cwd (&cwd);
     }
-  else if (chdir ("..") < 0)
+  else
     {
-      error (0, errno, _("cannot change back to directory %s via `..'"),
-            quote (full_filename (dir_name)));
-      return RM_ERROR;
+      struct stat sb;
+      if (chdir ("..") < 0)
+       {
+         error (0, errno, _("cannot change back to directory %s via `..'"),
+                quote (full_filename (dir_name)));
+         return RM_ERROR;
+       }
+
+      if (lstat (".", &sb))
+       error (EXIT_FAILURE, errno,
+              _("cannot lstat `.' in %s"), quote (full_filename (".")));
+
+      if (!SAME_INODE (sb, *cwd_dev_ino))
+       {
+         error (EXIT_FAILURE, 0,
+              _("ERROR: the directory %s initially had device/inode\n\
+numbers %lu/%lu, but now (after changing into at least one subdirectory\n\
+and changing back via `..'), the numbers for `.' are %lu/%lu.\n\
+That means that while rm was running, a partially-removed subdirectory\n\
+was moved to a different position in the file system hierarchy."),
+                quote (full_filename (".")),
+                (unsigned long)(cwd_dev_ino->st_dev),
+                (unsigned long)(cwd_dev_ino->st_ino),
+                (unsigned long)(sb.st_dev),
+                (unsigned long)(sb.st_ino));
+       }
     }
 
   if (x->interactive)
@@ -795,10 +826,13 @@ was replaced with either another directo
    things.  Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED
    if not.  If USER_SPECIFIED_NAME is non-zero, then the name part of FS may
    be `.', `..', or may contain slashes.  Otherwise, it must be a simple file
-   name (and hence must specify a file in the current directory).  */
+   name (and hence must specify a file in the current directory).
+   CWD_DEV_INO must store the device and inode numbers of the
+   current working directory.  */
 
 enum RM_status
-rm (struct File_spec *fs, int user_specified_name, const struct rm_options *x)
+rm (struct File_spec *fs, int user_specified_name,
+    struct rm_options const *x, struct dev_ino const *cwd_dev_ino)
 {
   mode_t filetype_mode;
 
@@ -884,7 +918,7 @@ The following two directories have the s
       if (need_save_cwd)
        need_save_cwd = (strchr (fs->filename, '/') != NULL);
 
-      status = remove_dir (fs, need_save_cwd, x);
+      status = remove_dir (fs, need_save_cwd, x, cwd_dev_ino);
 
 #ifdef ENABLE_CYCLE_CHECK
       {
Index: src/rm.c
===================================================================
RCS file: /fetish/fileutils/src/rm.c,v
retrieving revision 1.107
retrieving revision 1.108
diff -u -p -r1.107 -r1.108
--- src/rm.c    2001/12/02 22:26:58     1.107
+++ src/rm.c    2002/03/08 16:46:11     1.108
@@ -1,5 +1,5 @@
 /* `rm' file deletion utility for GNU.
-   Copyright (C) 88, 90, 91, 1994-2001 Free Software Foundation, Inc.
+   Copyright (C) 88, 90, 91, 1994-2002 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -187,17 +187,31 @@ main (int argc, char **argv)
 
   remove_init ();
 
-  for (; optind < argc; optind++)
-    {
-      struct File_spec fs;
-      enum RM_status status;
-
-      fspec_init_file (&fs, argv[optind]);
-      status = rm (&fs, 1, &x);
-      assert (VALID_STATUS (status));
-      if (status == RM_ERROR)
-       fail = 1;
-    }
+  {
+    struct stat cwd_sb;
+    struct dev_ino cwd_dev_ino;
+
+    /* FIXME: this lstat is not always necessary -- e.g., if there are no
+       directories, or if all directories arguments are specified via
+       absolute names.  */
+    if (lstat (".", &cwd_sb))
+      error (EXIT_FAILURE, errno, _("cannot lstat `.'"));
+
+    cwd_dev_ino.st_dev = cwd_sb.st_dev;
+    cwd_dev_ino.st_ino = cwd_sb.st_ino;
+
+    for (; optind < argc; optind++)
+      {
+       struct File_spec fs;
+       enum RM_status status;
+
+       fspec_init_file (&fs, argv[optind]);
+       status = rm (&fs, 1, &x, &cwd_dev_ino);
+       assert (VALID_STATUS (status));
+       if (status == RM_ERROR)
+         fail = 1;
+      }
+  }
 
   remove_fini ();
 
Index: src/mv.c
===================================================================
RCS file: /fetish/fileutils/src/mv.c,v
retrieving revision 1.131
retrieving revision 1.132
diff -u -p -r1.131 -r1.132
--- src/mv.c    2001/12/02 22:26:58     1.131
+++ src/mv.c    2002/03/08 16:45:51     1.132
@@ -1,5 +1,5 @@
 /* mv -- move or rename files
-   Copyright (C) 86, 89, 90, 91, 1995-2001 Free Software Foundation, Inc.
+   Copyright (C) 86, 89, 90, 91, 1995-2002 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -241,7 +241,20 @@ do_move (const char *source, const char 
          struct rm_options rm_options;
          struct File_spec fs;
          enum RM_status status;
+         static int first_rm = 1;
+         static struct dev_ino cwd_dev_ino;
 
+         if (first_rm)
+           {
+             struct stat cwd_sb;
+             if (lstat (".", &cwd_sb))
+               error (EXIT_FAILURE, errno, _("cannot lstat `.'"));
+
+             first_rm = 0;
+             cwd_dev_ino.st_dev = cwd_sb.st_dev;
+             cwd_dev_ino.st_ino = cwd_sb.st_ino;
+           }
+
          rm_option_init (&rm_options);
          rm_options.verbose = x->verbose;
 
@@ -253,7 +266,7 @@ do_move (const char *source, const char 
             took the else branch of movefile.  */
          strip_trailing_slashes (fs.filename);
 
-         status = rm (&fs, 1, &rm_options);
+         status = rm (&fs, 1, &rm_options, &cwd_dev_ino);
          assert (VALID_STATUS (status));
          if (status == RM_ERROR)
            fail = 1;
Index: src/remove.h
===================================================================
RCS file: /fetish/fileutils/src/remove.h,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -p -r1.3 -r1.4
--- src/remove.h        2000/10/16 08:10:58     1.3
+++ src/remove.h        2002/03/08 16:46:26     1.4
@@ -44,8 +44,16 @@ struct File_spec
   dev_t st_dev;
 };
 
-enum RM_status rm PARAMS ((struct File_spec *fs, int user_specified_name,
-                          const struct rm_options *x));
+struct dev_ino
+{
+  ino_t st_ino;
+  dev_t st_dev;
+};
+
+enum RM_status rm PARAMS ((struct File_spec *fs,
+                          int user_specified_name,
+                          struct rm_options const *x,
+                          struct dev_ino const *cwd_dev_ino));
 void fspec_init_file PARAMS ((struct File_spec *fs, const char *filename));
 void remove_init PARAMS ((void));
 void remove_fini PARAMS ((void));



reply via email to

[Prev in Thread] Current Thread [Next in Thread]