LCOV - code coverage report
Current view: top level - src - chown-core.c (source / functions) Hit Total Coverage
Test: coreutils.info Lines: 126 199 63.3 %
Date: 2018-01-30 Functions: 7 8 87.5 %

          Line data    Source code
       1             : /* chown-core.c -- core functions for changing ownership.
       2             :    Copyright (C) 2000, 2002-2008 Free Software Foundation, Inc.
       3             : 
       4             :    This program is free software: you can redistribute it and/or modify
       5             :    it under the terms of the GNU General Public License as published by
       6             :    the Free Software Foundation, either version 3 of the License, or
       7             :    (at your option) any later version.
       8             : 
       9             :    This program is distributed in the hope that it will be useful,
      10             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :    GNU General Public License for more details.
      13             : 
      14             :    You should have received a copy of the GNU General Public License
      15             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      16             : 
      17             : /* Extracted from chown.c/chgrp.c and librarified by Jim Meyering.  */
      18             : 
      19             : #include <config.h>
      20             : #include <stdio.h>
      21             : #include <sys/types.h>
      22             : #include <pwd.h>
      23             : #include <grp.h>
      24             : 
      25             : #include "system.h"
      26             : #include "chown-core.h"
      27             : #include "error.h"
      28             : #include "inttostr.h"
      29             : #include "quote.h"
      30             : #include "root-dev-ino.h"
      31             : #include "xfts.h"
      32             : 
      33             : #define FTSENT_IS_DIRECTORY(E)  \
      34             :   ((E)->fts_info == FTS_D    \
      35             :    || (E)->fts_info == FTS_DC        \
      36             :    || (E)->fts_info == FTS_DP        \
      37             :    || (E)->fts_info == FTS_DNR)
      38             : 
      39             : enum RCH_status
      40             :   {
      41             :     /* we called fchown and close, and both succeeded */
      42             :     RC_ok = 2,
      43             : 
      44             :     /* required_uid and/or required_gid are specified, but don't match */
      45             :     RC_excluded,
      46             : 
      47             :     /* SAME_INODE check failed */
      48             :     RC_inode_changed,
      49             : 
      50             :     /* open/fchown isn't needed, isn't safe, or doesn't work due to
      51             :        permissions problems; fall back on chown */
      52             :     RC_do_ordinary_chown,
      53             : 
      54             :     /* open, fstat, fchown, or close failed */
      55             :     RC_error
      56             :   };
      57             : 
      58             : extern void
      59         180 : chopt_init (struct Chown_option *chopt)
      60             : {
      61         180 :   chopt->verbosity = V_off;
      62         180 :   chopt->root_dev_ino = NULL;
      63         180 :   chopt->affect_symlink_referent = true;
      64         180 :   chopt->recurse = false;
      65         180 :   chopt->force_silent = false;
      66         180 :   chopt->user_name = NULL;
      67         180 :   chopt->group_name = NULL;
      68         180 : }
      69             : 
      70             : extern void
      71          56 : chopt_free (struct Chown_option *chopt ATTRIBUTE_UNUSED)
      72             : {
      73             :   /* Deliberately do not free chopt->user_name or ->group_name.
      74             :      They're not always allocated.  */
      75          56 : }
      76             : 
      77             : /* Convert the numeric group-id, GID, to a string stored in xmalloc'd memory,
      78             :    and return it.  If there's no corresponding group name, use the decimal
      79             :    representation of the ID.  */
      80             : 
      81             : extern char *
      82           4 : gid_to_name (gid_t gid)
      83             : {
      84             :   char buf[INT_BUFSIZE_BOUND (intmax_t)];
      85           4 :   struct group *grp = getgrgid (gid);
      86           4 :   return xstrdup (grp ? grp->gr_name
      87             :                   : TYPE_SIGNED (gid_t) ? imaxtostr (gid, buf)
      88           0 :                   : umaxtostr (gid, buf));
      89             : }
      90             : 
      91             : /* Convert the numeric user-id, UID, to a string stored in xmalloc'd memory,
      92             :    and return it.  If there's no corresponding user name, use the decimal
      93             :    representation of the ID.  */
      94             : 
      95             : extern char *
      96           0 : uid_to_name (uid_t uid)
      97             : {
      98             :   char buf[INT_BUFSIZE_BOUND (intmax_t)];
      99           0 :   struct passwd *pwd = getpwuid (uid);
     100           0 :   return xstrdup (pwd ? pwd->pw_name
     101             :                   : TYPE_SIGNED (uid_t) ? imaxtostr (uid, buf)
     102           0 :                   : umaxtostr (uid, buf));
     103             : }
     104             : 
     105             : /* Tell the user how/if the user and group of FILE have been changed.
     106             :    If USER is NULL, give the group-oriented messages.
     107             :    CHANGED describes what (if anything) has happened. */
     108             : 
     109             : static void
     110           3 : describe_change (const char *file, enum Change_status changed,
     111             :                  char const *user, char const *group)
     112             : {
     113             :   const char *fmt;
     114             :   char const *spec;
     115           3 :   char *spec_allocated = NULL;
     116             : 
     117           3 :   if (changed == CH_NOT_APPLIED)
     118             :     {
     119           0 :       printf (_("neither symbolic link %s nor referent has been changed\n"),
     120             :               quote (file));
     121           0 :       return;
     122             :     }
     123             : 
     124           3 :   if (user)
     125             :     {
     126           0 :       if (group)
     127             :         {
     128           0 :           spec_allocated = xmalloc (strlen (user) + 1 + strlen (group) + 1);
     129           0 :           stpcpy (stpcpy (stpcpy (spec_allocated, user), ":"), group);
     130           0 :           spec = spec_allocated;
     131             :         }
     132             :       else
     133             :         {
     134           0 :           spec = user;
     135             :         }
     136             :     }
     137             :   else
     138             :     {
     139           3 :       spec = group;
     140             :     }
     141             : 
     142           3 :   switch (changed)
     143             :     {
     144           0 :     case CH_SUCCEEDED:
     145           0 :       fmt = (user ? _("changed ownership of %s to %s\n")
     146           0 :              : group ? _("changed group of %s to %s\n")
     147           0 :              : _("no change to ownership of %s\n"));
     148           0 :       break;
     149           2 :     case CH_FAILED:
     150           2 :       fmt = (user ? _("failed to change ownership of %s to %s\n")
     151           4 :              : group ? _("failed to change group of %s to %s\n")
     152           2 :              : _("failed to change ownership of %s\n"));
     153           2 :       break;
     154           1 :     case CH_NO_CHANGE_REQUESTED:
     155           1 :       fmt = (user ? _("ownership of %s retained as %s\n")
     156           2 :              : group ? _("group of %s retained as %s\n")
     157           1 :              : _("ownership of %s retained\n"));
     158           1 :       break;
     159           0 :     default:
     160           0 :       abort ();
     161             :     }
     162             : 
     163           3 :   printf (fmt, quote (file), spec);
     164             : 
     165           3 :   free (spec_allocated);
     166             : }
     167             : 
     168             : /* Change the owner and/or group of the FILE to UID and/or GID (safely)
     169             :    only if REQUIRED_UID and REQUIRED_GID match the owner and group IDs
     170             :    of FILE.  ORIG_ST must be the result of `stat'ing FILE.
     171             : 
     172             :    The `safely' part above means that we can't simply use chown(2),
     173             :    since FILE might be replaced with some other file between the time
     174             :    of the preceding stat/lstat and this chown call.  So here we open
     175             :    FILE and do everything else via the resulting file descriptor.
     176             :    We first call fstat and verify that the dev/inode match those from
     177             :    the preceding stat call, and only then, if appropriate (given the
     178             :    required_uid and required_gid constraints) do we call fchown.
     179             : 
     180             :    Return RC_do_ordinary_chown if we can't open FILE, or if FILE is a
     181             :    special file that might have undesirable side effects when opening.
     182             :    In this case the caller can use the less-safe ordinary chown.
     183             : 
     184             :    Return one of the RCH_status values.  */
     185             : 
     186             : static enum RCH_status
     187          30 : restricted_chown (int cwd_fd, char const *file,
     188             :                   struct stat const *orig_st,
     189             :                   uid_t uid, gid_t gid,
     190             :                   uid_t required_uid, gid_t required_gid)
     191             : {
     192          30 :   enum RCH_status status = RC_ok;
     193             :   struct stat st;
     194          30 :   int open_flags = O_NONBLOCK | O_NOCTTY;
     195             :   int fd;
     196             : 
     197          30 :   if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1)
     198          30 :     return RC_do_ordinary_chown;
     199             : 
     200           0 :   if (! S_ISREG (orig_st->st_mode))
     201             :     {
     202           0 :       if (S_ISDIR (orig_st->st_mode))
     203           0 :         open_flags |= O_DIRECTORY;
     204             :       else
     205           0 :         return RC_do_ordinary_chown;
     206             :     }
     207             : 
     208           0 :   fd = openat (cwd_fd, file, O_RDONLY | open_flags);
     209           0 :   if (! (0 <= fd
     210           0 :          || (errno == EACCES && S_ISREG (orig_st->st_mode)
     211           0 :              && 0 <= (fd = openat (cwd_fd, file, O_WRONLY | open_flags)))))
     212           0 :     return (errno == EACCES ? RC_do_ordinary_chown : RC_error);
     213             : 
     214           0 :   if (fstat (fd, &st) != 0)
     215           0 :     status = RC_error;
     216           0 :   else if (! SAME_INODE (*orig_st, st))
     217           0 :     status = RC_inode_changed;
     218           0 :   else if ((required_uid == (uid_t) -1 || required_uid == st.st_uid)
     219           0 :            && (required_gid == (gid_t) -1 || required_gid == st.st_gid))
     220             :     {
     221           0 :       if (fchown (fd, uid, gid) == 0)
     222             :         {
     223           0 :           status = (close (fd) == 0
     224           0 :                     ? RC_ok : RC_error);
     225           0 :           return status;
     226             :         }
     227             :       else
     228             :         {
     229           0 :           status = RC_error;
     230             :         }
     231             :     }
     232             : 
     233             :   { /* FIXME: remove these curly braces when we assume C99.  */
     234           0 :     int saved_errno = errno;
     235           0 :     close (fd);
     236           0 :     errno = saved_errno;
     237           0 :     return status;
     238             :   }
     239             : }
     240             : 
     241             : /* Change the owner and/or group of the file specified by FTS and ENT
     242             :    to UID and/or GID as appropriate.
     243             :    If REQUIRED_UID is not -1, then skip files with any other user ID.
     244             :    If REQUIRED_GID is not -1, then skip files with any other group ID.
     245             :    CHOPT specifies additional options.
     246             :    Return true if successful.  */
     247             : static bool
     248      293421 : change_file_owner (FTS *fts, FTSENT *ent,
     249             :                    uid_t uid, gid_t gid,
     250             :                    uid_t required_uid, gid_t required_gid,
     251             :                    struct Chown_option const *chopt)
     252             : {
     253      293421 :   char const *file_full_name = ent->fts_path;
     254      293421 :   char const *file = ent->fts_accpath;
     255             :   struct stat const *file_stats;
     256             :   struct stat stat_buf;
     257      293421 :   bool ok = true;
     258             :   bool do_chown;
     259      293421 :   bool symlink_changed = true;
     260             : 
     261      293421 :   switch (ent->fts_info)
     262             :     {
     263       26535 :     case FTS_D:
     264       26535 :       if (chopt->recurse)
     265             :         {
     266       26514 :           if (ROOT_DEV_INO_CHECK (chopt->root_dev_ino, ent->fts_statp))
     267             :             {
     268             :               /* This happens e.g., with "chown -R --preserve-root 0 /"
     269             :                  and with "chown -RH --preserve-root 0 symlink-to-root".  */
     270           0 :               ROOT_DEV_INO_WARN (file_full_name);
     271             :               /* Tell fts not to traverse into this hierarchy.  */
     272           0 :               fts_set (fts, ent, FTS_SKIP);
     273             :               /* Ensure that we do not process "/" on the second visit.  */
     274           0 :               ent = fts_read (fts);
     275           0 :               return false;
     276             :             }
     277       26514 :           return true;
     278             :         }
     279          21 :       break;
     280             : 
     281       26521 :     case FTS_DP:
     282       26521 :       if (! chopt->recurse)
     283          21 :         return true;
     284       26500 :       break;
     285             : 
     286          76 :     case FTS_NS:
     287             :       /* For a top-level file or directory, this FTS_NS (stat failed)
     288             :          indicator is determined at the time of the initial fts_open call.
     289             :          With programs like chmod, chown, and chgrp, that modify
     290             :          permissions, it is possible that the file in question is
     291             :          accessible when control reaches this point.  So, if this is
     292             :          the first time we've seen the FTS_NS for this file, tell
     293             :          fts_read to stat it "again".  */
     294          76 :       if (ent->fts_level == 0 && ent->fts_number == 0)
     295             :         {
     296          38 :           ent->fts_number = 1;
     297          38 :           fts_set (fts, ent, FTS_AGAIN);
     298          38 :           return true;
     299             :         }
     300          38 :       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
     301          38 :       ok = false;
     302          38 :       break;
     303             : 
     304           0 :     case FTS_ERR:
     305           0 :       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
     306           0 :       ok = false;
     307           0 :       break;
     308             : 
     309          14 :     case FTS_DNR:
     310          14 :       error (0, ent->fts_errno, _("cannot read directory %s"),
     311             :              quote (file_full_name));
     312          14 :       ok = false;
     313          14 :       break;
     314             : 
     315      240275 :     default:
     316      240275 :       break;
     317             :     }
     318             : 
     319      266848 :   if (!ok)
     320             :     {
     321          52 :       do_chown = false;
     322          52 :       file_stats = NULL;
     323             :     }
     324      266796 :   else if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1
     325      266796 :            && chopt->verbosity == V_off
     326      266794 :            && ! chopt->root_dev_ino
     327      266794 :            && ! chopt->affect_symlink_referent)
     328             :     {
     329      266766 :       do_chown = true;
     330      266766 :       file_stats = ent->fts_statp;
     331             :     }
     332             :   else
     333             :     {
     334          30 :       file_stats = ent->fts_statp;
     335             : 
     336             :       /* If this is a symlink and we're dereferencing them,
     337             :          stat it to get info on the referent.  */
     338          30 :       if (chopt->affect_symlink_referent && S_ISLNK (file_stats->st_mode))
     339             :         {
     340           2 :           if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
     341             :             {
     342           0 :               error (0, errno, _("cannot dereference %s"),
     343             :                      quote (file_full_name));
     344           0 :               ok = false;
     345             :             }
     346             : 
     347           2 :           file_stats = &stat_buf;
     348             :         }
     349             : 
     350          30 :       do_chown = (ok
     351          30 :                   && (required_uid == (uid_t) -1
     352           0 :                       || required_uid == file_stats->st_uid)
     353          90 :                   && (required_gid == (gid_t) -1
     354           0 :                       || required_gid == file_stats->st_gid));
     355             :     }
     356             : 
     357             :   /* This happens when chown -LR --preserve-root encounters a symlink-to-/.  */
     358      266848 :   if (ok
     359      266796 :       && FTSENT_IS_DIRECTORY (ent)
     360       26522 :       && ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats))
     361             :     {
     362           0 :       ROOT_DEV_INO_WARN (file_full_name);
     363           0 :       return false;
     364             :     }
     365             : 
     366      266848 :   if (do_chown)
     367             :     {
     368      266796 :       if ( ! chopt->affect_symlink_referent)
     369             :         {
     370      266766 :           ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0);
     371             : 
     372             :           /* Ignore any error due to lack of support; POSIX requires
     373             :              this behavior for top-level symbolic links with -h, and
     374             :              implies that it's required for all symbolic links.  */
     375      266766 :           if (!ok && errno == EOPNOTSUPP)
     376             :             {
     377           0 :               ok = true;
     378           0 :               symlink_changed = false;
     379             :             }
     380             :         }
     381             :       else
     382             :         {
     383             :           /* If possible, avoid a race condition with --from=O:G and without the
     384             :              (-h) --no-dereference option.  If fts's stat call determined
     385             :              that the uid/gid of FILE matched the --from=O:G-selected
     386             :              owner and group IDs, blindly using chown(2) here could lead
     387             :              chown(1) or chgrp(1) mistakenly to dereference a *symlink*
     388             :              to an arbitrary file that an attacker had moved into the
     389             :              place of FILE during the window between the stat and
     390             :              chown(2) calls.  If FILE is a regular file or a directory
     391             :              that can be opened, this race condition can be avoided safely.  */
     392             : 
     393          30 :           enum RCH_status err
     394          30 :             = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid,
     395             :                                 required_uid, required_gid);
     396          30 :           switch (err)
     397             :             {
     398           0 :             case RC_ok:
     399           0 :               break;
     400             : 
     401          30 :             case RC_do_ordinary_chown:
     402          30 :               ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0);
     403          30 :               break;
     404             : 
     405           0 :             case RC_error:
     406           0 :               ok = false;
     407           0 :               break;
     408             : 
     409           0 :             case RC_inode_changed:
     410             :               /* FIXME: give a diagnostic in this case?  */
     411             :             case RC_excluded:
     412           0 :               do_chown = false;
     413           0 :               ok = false;
     414           0 :               break;
     415             : 
     416           0 :             default:
     417           0 :               abort ();
     418             :             }
     419             :         }
     420             : 
     421             :       /* On some systems (e.g., Linux-2.4.x),
     422             :          the chown function resets the `special' permission bits.
     423             :          Do *not* restore those bits;  doing so would open a window in
     424             :          which a malicious user, M, could subvert a chown command run
     425             :          by some other user and operating on files in a directory
     426             :          where M has write access.  */
     427             : 
     428      266796 :       if (do_chown && !ok && ! chopt->force_silent)
     429      103760 :         error (0, errno, (uid != (uid_t) -1
     430             :                           ? _("changing ownership of %s")
     431             :                           : _("changing group of %s")),
     432             :                quote (file_full_name));
     433             :     }
     434             : 
     435      266848 :   if (chopt->verbosity != V_off)
     436             :     {
     437           6 :       bool changed =
     438           6 :         ((do_chown & ok & symlink_changed)
     439           6 :          && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid)
     440           0 :                && (gid == (gid_t) -1 || gid == file_stats->st_gid)));
     441             : 
     442           6 :       if (changed || chopt->verbosity == V_high)
     443             :         {
     444           3 :           enum Change_status ch_status =
     445           3 :             (!ok ? CH_FAILED
     446           4 :              : !symlink_changed ? CH_NOT_APPLIED
     447           1 :              : !changed ? CH_NO_CHANGE_REQUESTED
     448             :              : CH_SUCCEEDED);
     449           3 :           describe_change (file_full_name, ch_status,
     450           3 :                            chopt->user_name, chopt->group_name);
     451             :         }
     452             :     }
     453             : 
     454      266848 :   if ( ! chopt->recurse)
     455          57 :     fts_set (fts, ent, FTS_SKIP);
     456             : 
     457      266848 :   return ok;
     458             : }
     459             : 
     460             : /* Change the owner and/or group of the specified FILES.
     461             :    BIT_FLAGS specifies how to treat each symlink-to-directory
     462             :    that is encountered during a recursive traversal.
     463             :    CHOPT specifies additional options.
     464             :    If UID is not -1, then change the owner id of each file to UID.
     465             :    If GID is not -1, then change the group id of each file to GID.
     466             :    If REQUIRED_UID and/or REQUIRED_GID is not -1, then change only
     467             :    files with user ID and group ID that match the non-(-1) value(s).
     468             :    Return true if successful.  */
     469             : extern bool
     470          67 : chown_files (char **files, int bit_flags,
     471             :              uid_t uid, gid_t gid,
     472             :              uid_t required_uid, gid_t required_gid,
     473             :              struct Chown_option const *chopt)
     474             : {
     475          67 :   bool ok = true;
     476             : 
     477             :   /* Use lstat and stat only if they're needed.  */
     478         134 :   int stat_flags = ((required_uid != (uid_t) -1 || required_gid != (gid_t) -1
     479          67 :                      || chopt->affect_symlink_referent
     480           6 :                      || chopt->verbosity != V_off)
     481             :                     ? 0
     482         128 :                     : FTS_NOSTAT);
     483             : 
     484          67 :   FTS *fts = xfts_open (files, bit_flags | stat_flags, NULL);
     485             : 
     486             :   while (1)
     487      293421 :     {
     488             :       FTSENT *ent;
     489             : 
     490      293477 :       ent = fts_read (fts);
     491      293477 :       if (ent == NULL)
     492             :         {
     493          56 :           if (errno != 0)
     494             :             {
     495             :               /* FIXME: try to give a better message  */
     496           0 :               error (0, errno, _("fts_read failed"));
     497           0 :               ok = false;
     498             :             }
     499          56 :           break;
     500             :         }
     501             : 
     502      293421 :       ok &= change_file_owner (fts, ent, uid, gid,
     503             :                                required_uid, required_gid, chopt);
     504             :     }
     505             : 
     506             :   /* Ignore failure, since the only way it can do so is in failing to
     507             :      return to the original directory, and since we're about to exit,
     508             :      that doesn't matter.  */
     509          56 :   fts_close (fts);
     510             : 
     511          56 :   return ok;
     512             : }

Generated by: LCOV version 1.10