bug-coreutils
[Top][All Lists]
Advanced

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

bug#10311: RFE: Give chmod a "-h" option as well


From: Paul Eggert
Subject: bug#10311: RFE: Give chmod a "-h" option as well
Date: Fri, 16 Dec 2011 10:06:40 -0800
User-agent: Mozilla/5.0 (X11; Linux i686; rv:8.0) Gecko/20111124 Thunderbird/8.0

On 12/16/11 09:30, Bob Proulx wrote:
> Neither chmod -R nor find by default dereference symlinks.

But there's still a problem without -R.  Suppose the attacker does
"ln -s /etc/passwd slylink" and then root does "chmod a+w *"
in a directory containing slylink.  Then anyone can write /etc/passwd.

There's an obvious fix here: make chmod act like chown with respect
to the -H, -L, -P, and -h options.  This would be compatible with
FreeBSD chmod.  On hosts that support symbolic-link permissions
(BSD being one of them), GNU chmod would act like BSD chmod when
changing the mode of symbolic links.  On hosts that don't, it
would report an error.  Then a paranoid root (and shouldn't root
be paranoid? :-) could use chmod -h for everything.

Here's a quick and untested patch to do this.  Assuming there's interest,
I can flesh this out by adding proper documentation and test cases.

diff --git a/src/chmod.c b/src/chmod.c
index 6fec84a..6b7c021 100644
--- a/src/chmod.c
+++ b/src/chmod.c
@@ -68,6 +68,10 @@ static mode_t umask_value;
 /* If true, change the modes of directories recursively. */
 static bool recurse;
 
+/* 1 if --dereference, 0 if --no-dereference, -1 if neither has been
+   specified.  */
+static int dereference = -1;
+
 /* If true, force silence (suppress most of error messages). */
 static bool force_silent;
 
@@ -87,7 +91,8 @@ static struct dev_ino *root_dev_ino;
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  NO_PRESERVE_ROOT = CHAR_MAX + 1,
+  DEREFERENCE_OPTION = CHAR_MAX + 1,
+  NO_PRESERVE_ROOT,
   PRESERVE_ROOT,
   REFERENCE_FILE_OPTION
 };
@@ -95,7 +100,9 @@ enum
 static struct option const long_options[] =
 {
   {"changes", no_argument, NULL, 'c'},
+  {"dereference", no_argument, NULL, DEREFERENCE_OPTION},
   {"recursive", no_argument, NULL, 'R'},
+  {"no-dereference", no_argument, NULL, 'h'},
   {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
   {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
   {"quiet", no_argument, NULL, 'f'},
@@ -188,6 +195,7 @@ process_file (FTS *fts, FTSENT *ent)
   char const *file_full_name = ent->fts_path;
   char const *file = ent->fts_accpath;
   const struct stat *file_stats = ent->fts_statp;
+  struct stat stat_buf;
   mode_t old_mode IF_LINT ( = 0);
   mode_t new_mode IF_LINT ( = 0);
   bool ok = true;
@@ -232,10 +240,28 @@ process_file (FTS *fts, FTSENT *ent)
       break;
 
     case FTS_SLNONE:
-      if (! force_silent)
-        error (0, 0, _("cannot operate on dangling symlink %s"),
-               quote (file_full_name));
-      ok = false;
+      if (dereference)
+        {
+          if (! force_silent)
+            error (0, 0, _("cannot operate on dangling symlink %s"),
+                   quote (file_full_name));
+          ok = false;
+        }
+      break;
+
+    case FTS_SL:
+      if (dereference)
+        {
+          if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
+            {
+              if (! force_silent)
+                error (0, errno, _("cannot dereference %s"),
+                       quote (file_full_name));
+              ok = false;
+            }
+
+          file_stats = &stat_buf;
+        }
       break;
 
     case FTS_DC:               /* directory that causes cycles */
@@ -266,17 +292,16 @@ process_file (FTS *fts, FTSENT *ent)
       new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
                               change, NULL);
 
-      if (! S_ISLNK (old_mode))
+      if (fchmodat (fts->fts_cwd_fd, file, new_mode,
+                    dereference ? 0 : AT_SYMLINK_NOFOLLOW)
+          == 0)
+        chmod_succeeded = true;
+      else
         {
-          if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
-            chmod_succeeded = true;
-          else
-            {
-              if (! force_silent)
-                error (0, errno, _("changing permissions of %s"),
-                       quote (file_full_name));
-              ok = false;
-            }
+          if (! force_silent)
+            error (0, errno, _("changing permissions of %s"),
+                   quote (file_full_name));
+          ok = false;
         }
     }
 
@@ -381,6 +406,13 @@ Change the mode of each FILE to MODE.\n\
   -c, --changes           like verbose but report only when a change is made\n\
 "), stdout);
       fputs (_("\
+      --dereference      affect the referent of each symbolic link (this is\n\
+                         the default), rather than the symbolic link itself\n\
+  -h, --no-dereference   affect each symbolic link instead of any referenced\n\
+                         file (useful only on systems that can change the\n\
+                         ownership of a symlink)\n\
+"), stdout);
+      fputs (_("\
       --no-preserve-root  do not treat `/' specially (the default)\n\
       --preserve-root     fail to operate recursively on `/'\n\
 "), stdout);
@@ -389,6 +421,19 @@ Change the mode of each FILE to MODE.\n\
   -v, --verbose           output a diagnostic for every file processed\n\
       --reference=RFILE   use RFILE's mode instead of MODE values\n\
   -R, --recursive         change files and directories recursively\n\
+\n\
+"), stdout);
+      fputs (_("\
+The following options modify how a hierarchy is traversed when the -R\n\
+option is also specified.  If more than one is specified, only the final\n\
+one takes effect.\n\
+\n\
+  -H                     if a command line argument is a symbolic link\n\
+                         to a directory, traverse it\n\
+  -L                     traverse every symbolic link to a directory\n\
+                         encountered\n\
+  -P                     do not traverse any symbolic links (default)\n\
+\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -414,6 +459,7 @@ main (int argc, char **argv)
   bool preserve_root = false;
   char const *reference_file = NULL;
   int c;
+  int bit_flags = FTS_PHYSICAL;
 
   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
@@ -426,12 +472,33 @@ main (int argc, char **argv)
   recurse = force_silent = diagnose_surprises = false;
 
   while ((c = getopt_long (argc, argv,
-                           "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
+                           "HLPRcfhvr::w::x::X::s::t::u::g::o::a::,::+::=::",
                            long_options, NULL))
          != -1)
     {
       switch (c)
         {
+        case 'H': /* Traverse command-line symlinks-to-directories.  */
+          bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL;
+          break;
+
+        case 'L': /* Traverse all symlinks-to-directories.  */
+          bit_flags = FTS_LOGICAL;
+          break;
+
+        case 'P': /* Traverse no symlinks-to-directories.  */
+          bit_flags = FTS_PHYSICAL;
+          break;
+
+        case 'h': /* --no-dereference: affect symlinks */
+          dereference = 0;
+          break;
+
+        case DEREFERENCE_OPTION: /* --dereference: affect the referent
+                                    of each symlink */
+          dereference = 1;
+          break;
+
         case 'r':
         case 'w':
         case 'x':
@@ -499,6 +566,21 @@ main (int argc, char **argv)
         }
     }
 
+  if (recurse)
+    {
+      if (bit_flags == FTS_PHYSICAL)
+        {
+          if (dereference == 1)
+            error (EXIT_FAILURE, 0,
+                   _("-R --dereference requires either -H or -L"));
+          dereference = 0;
+        }
+    }
+  else
+    {
+      bit_flags = FTS_PHYSICAL;
+    }
+
   if (reference_file)
     {
       if (mode)
@@ -553,8 +635,8 @@ main (int argc, char **argv)
       root_dev_ino = NULL;
     }
 
-  ok = process_files (argv + optind,
-                      FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
+  bit_flags |= FTS_DEFER_STAT;
+  ok = process_files (argv + optind, bit_flags);
 
   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }





reply via email to

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