Browse Source

* NEWS: Fix some race conditions with tar -x --same-owner.
* src/extract.c (ARCHIVED_PERMSTATS): Add a comment saying that
S_IRWXG | S_IRWXO might be masked out.
(set_mode): Set the mode if some bits were masked out originally.
(set_stat): Don't chmod before chown, as that might temporarily
grant permissions that we don't want to grant. The chmod was
there only to work around broken hosts, so add a comment advising
users not to use those broken hosts instead.
(repair_delayed_set_stat, extract_dir):
Remember to mask out current umask before inverting permissions.
(extract_dir): If the owner might change, or if the mode has
special bits, create the directory 700 at first, but restore it later.
(open_output_file): New arg mode; all uses changed.
(extract_file, extract_node, extract_fifo): If the owner might
change, omit group and other bits at first, but restore them after
changing the owner.

Paul Eggert 18 years ago
parent
commit
908d78d208
3 changed files with 81 additions and 36 deletions
  1. 19 0
      ChangeLog
  2. 4 0
      NEWS
  3. 58 36
      src/extract.c

+ 19 - 0
ChangeLog

@@ -1,3 +1,22 @@
+2006-12-04  Paul Eggert  <eggert@cs.ucla.edu>
+
+	* NEWS: Fix some race conditions with tar -x --same-owner.
+	* src/extract.c (ARCHIVED_PERMSTATS): Add a comment saying that
+	S_IRWXG | S_IRWXO might be masked out.
+	(set_mode): Set the mode if some bits were masked out originally.
+	(set_stat): Don't chmod before chown, as that might temporarily
+	grant permissions that we don't want to grant.  The chmod was
+	there only to work around broken hosts, so add a comment advising
+	users not to use those broken hosts instead.
+	(repair_delayed_set_stat, extract_dir):
+	Remember to mask out current umask before inverting permissions.
+	(extract_dir): If the owner might change, or if the mode has
+	special bits, create the directory 700 at first, but restore it later.
+	(open_output_file): New arg mode; all uses changed.
+	(extract_file, extract_node, extract_fifo): If the owner might
+	change, omit group and other bits at first, but restore them after
+	changing the owner.
+
 2006-12-04  Jim Meyering  <jim@meyering.net>
 2006-12-04  Jim Meyering  <jim@meyering.net>
 
 
 	* doc/tar.texi (Long Options): Remove doubled word.
 	* doc/tar.texi (Long Options): Remove doubled word.

+ 4 - 0
NEWS

@@ -9,6 +9,10 @@ Please send GNU tar bug reports to <bug-tar@gnu.org>
   records, but if you have one and you trust its contents, you can
   records, but if you have one and you trust its contents, you can
   decode it with GNU tar 1.16 or earlier.
   decode it with GNU tar 1.16 or earlier.
 
 
+* Race conditions have been fixed that in some cases briefly allowed
+  files extracted by 'tar -x --same-owner' (or plain 'tar -x', when
+  running as root) to be accessed by users that they shouldn't have been.
+
 version 1.16 - Sergey Poznyakoff, 2006-10-21
 version 1.16 - Sergey Poznyakoff, 2006-10-21
 
 
 * After creating an archive, tar exits with code 1 if some files were
 * After creating an archive, tar exits with code 1 if some files were

+ 58 - 36
src/extract.c

@@ -37,7 +37,8 @@ enum permstatus
   /* This file may have existed already; its permissions are unknown.  */
   /* This file may have existed already; its permissions are unknown.  */
   UNKNOWN_PERMSTATUS,
   UNKNOWN_PERMSTATUS,
 
 
-  /* This file was created using the permissions from the archive.  */
+  /* This file was created using the permissions from the archive,
+     except with S_IRWXG | S_IRWXO masked out if 0 < same_owner_option.  */
   ARCHIVED_PERMSTATUS,
   ARCHIVED_PERMSTATUS,
 
 
   /* This is an intermediate directory; the archive did not specify
   /* This is an intermediate directory; the archive did not specify
@@ -149,12 +150,15 @@ set_mode (char const *file_name,
     {
     {
       mode = stat_info->st_mode;
       mode = stat_info->st_mode;
 
 
-      /* If we created the file and it has a usual mode, then its mode
+      /* If we created the file and it has a mode that we set already
-	 is normally set correctly already.  But on many hosts, some
+	 with O_CREAT, then its mode is often set correctly already.
+	 But if we are changing ownership, the mode's group and and
+	 other permission bits were omitted originally, so it's less
+	 likely that the mode is OK now.  Also, on many hosts, some
 	 directories inherit the setgid bits from their parents, so we
 	 directories inherit the setgid bits from their parents, so we
 	 we must set directories' modes explicitly.  */
 	 we must set directories' modes explicitly.  */
-      if (permstatus == ARCHIVED_PERMSTATUS
+      if ((permstatus == ARCHIVED_PERMSTATUS
-	  && ! (mode & ~ MODE_RWX)
+	   && ! (mode & ~ (0 < same_owner_option ? S_IRWXU : MODE_RWX)))
 	  && typeflag != DIRTYPE
 	  && typeflag != DIRTYPE
 	  && typeflag != GNUTYPE_DUMPDIR)
 	  && typeflag != GNUTYPE_DUMPDIR)
 	return;
 	return;
@@ -217,7 +221,7 @@ check_time (char const *file_name, struct timespec t)
 /* Restore stat attributes (owner, group, mode and times) for
 /* Restore stat attributes (owner, group, mode and times) for
    FILE_NAME, using information given in *ST.
    FILE_NAME, using information given in *ST.
    If CUR_INFO is nonzero, *CUR_INFO is the
    If CUR_INFO is nonzero, *CUR_INFO is the
-   file's currernt status.
+   file's current status.
    If not restoring permissions, invert the
    If not restoring permissions, invert the
    INVERT_PERMISSIONS bits from the file's current permissions.
    INVERT_PERMISSIONS bits from the file's current permissions.
    PERMSTATUS specifies the status of the file's permissions.
    PERMSTATUS specifies the status of the file's permissions.
@@ -265,11 +269,11 @@ set_stat (char const *file_name,
 	}
 	}
 
 
       /* Some systems allow non-root users to give files away.  Once this
       /* Some systems allow non-root users to give files away.  Once this
-	 done, it is not possible anymore to change file permissions, so we
+	 done, it is not possible anymore to change file permissions.
-	 have to set permissions prior to possibly giving files away.  */
+	 However, setting file permissions now would be incorrect, since
-
+	 they would apply to the wrong user, and there would be a race
-      set_mode (file_name, &st->stat, cur_info,
+	 condition.  So, don't use systems that allow non-root users to
-		invert_permissions, permstatus, typeflag);
+	 give files away.  */
     }
     }
 
 
   if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
   if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
@@ -278,29 +282,36 @@ set_stat (char const *file_name,
 	 the symbolic link itself.  In this case, a mere chown would change
 	 the symbolic link itself.  In this case, a mere chown would change
 	 the attributes of the file the symbolic link is pointing to, and
 	 the attributes of the file the symbolic link is pointing to, and
 	 should be avoided.  */
 	 should be avoided.  */
+      int chown_result = 1;
 
 
       if (typeflag == SYMTYPE)
       if (typeflag == SYMTYPE)
 	{
 	{
 #if HAVE_LCHOWN
 #if HAVE_LCHOWN
-	  if (lchown (file_name, st->stat.st_uid, st->stat.st_gid) < 0)
+	  chown_result = lchown (file_name, st->stat.st_uid, st->stat.st_gid);
-	    chown_error_details (file_name,
-				 st->stat.st_uid, st->stat.st_gid);
 #endif
 #endif
 	}
 	}
       else
       else
 	{
 	{
-	  if (chown (file_name, st->stat.st_uid, st->stat.st_gid) < 0)
+	  chown_result = chown (file_name, st->stat.st_uid, st->stat.st_gid);
-	    chown_error_details (file_name,
+	}
-				 st->stat.st_uid, st->stat.st_gid);
+
-
+      if (chown_result == 0)
-	  /* On a few systems, and in particular, those allowing to give files
+	{
-	     away, changing the owner or group destroys the suid or sgid bits.
+	  /* Changing the owner can flip st_mode bits in some cases, so
-	     So let's attempt setting these bits once more.  */
+	     ignore cur_info if it might be obsolete now.  */
-	  if (st->stat.st_mode & (S_ISUID | S_ISGID | S_ISVTX))
+	  if (cur_info
-	    set_mode (file_name, &st->stat, 0,
+	      && cur_info->st_mode & S_IXUGO
-		      invert_permissions, permstatus, typeflag);
+	      && cur_info->st_mode & (S_ISUID | S_ISGID))
+	    cur_info = NULL;
 	}
 	}
+      else if (chown_result < 0)
+	chown_error_details (file_name,
+			     st->stat.st_uid, st->stat.st_gid);
     }
     }
+
+  if (typeflag != SYMTYPE)
+    set_mode (file_name, &st->stat, cur_info,
+	      invert_permissions, permstatus, typeflag);
 }
 }
 
 
 /* Remember to restore stat attributes (owner, group, mode and times)
 /* Remember to restore stat attributes (owner, group, mode and times)
@@ -374,7 +385,8 @@ repair_delayed_set_stat (char const *dir,
 	  data->atime = current_stat_info.atime;
 	  data->atime = current_stat_info.atime;
 	  data->mtime = current_stat_info.mtime;
 	  data->mtime = current_stat_info.mtime;
 	  data->invert_permissions =
 	  data->invert_permissions =
-	    (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode));
+	    ((current_stat_info.stat.st_mode ^ st.st_mode)
+	     & MODE_RWX & ~ current_umask);
 	  data->permstatus = ARCHIVED_PERMSTATUS;
 	  data->permstatus = ARCHIVED_PERMSTATUS;
 	  return;
 	  return;
 	}
 	}
@@ -626,8 +638,9 @@ extract_dir (char *file_name, int typeflag)
   else if (typeflag == GNUTYPE_DUMPDIR)
   else if (typeflag == GNUTYPE_DUMPDIR)
     skip_member ();
     skip_member ();
 
 
-  mode = (current_stat_info.stat.st_mode |
+  mode = current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR);
-	   (we_are_root ? 0 : MODE_WXUSR)) & MODE_RWX;
+  if (0 < same_owner_option || current_stat_info.stat.st_mode & ~ MODE_RWX)
+    mode &= S_IRWXU;
 
 
   while ((status = mkdir (file_name, mode)))
   while ((status = mkdir (file_name, mode)))
     {
     {
@@ -670,7 +683,8 @@ extract_dir (char *file_name, int typeflag)
     {
     {
       if (status == 0)
       if (status == 0)
 	delay_set_stat (file_name, &current_stat_info,
 	delay_set_stat (file_name, &current_stat_info,
-			MODE_RWX & (mode ^ current_stat_info.stat.st_mode),
+			((mode ^ current_stat_info.stat.st_mode)
+			 & MODE_RWX & ~ current_umask),
 			ARCHIVED_PERMSTATUS);
 			ARCHIVED_PERMSTATUS);
       else /* For an already existing directory, invert_perms must be 0 */
       else /* For an already existing directory, invert_perms must be 0 */
 	delay_set_stat (file_name, &current_stat_info,
 	delay_set_stat (file_name, &current_stat_info,
@@ -682,14 +696,13 @@ extract_dir (char *file_name, int typeflag)
 
 
 
 
 static int
 static int
-open_output_file (char *file_name, int typeflag)
+open_output_file (char *file_name, int typeflag, mode_t mode)
 {
 {
   int fd;
   int fd;
   int openflag = (O_WRONLY | O_BINARY | O_CREAT
   int openflag = (O_WRONLY | O_BINARY | O_CREAT
 		  | (old_files_option == OVERWRITE_OLD_FILES
 		  | (old_files_option == OVERWRITE_OLD_FILES
 		     ? O_TRUNC
 		     ? O_TRUNC
 		     : O_EXCL));
 		     : O_EXCL));
-  mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
 
 
 #if O_CTG
 #if O_CTG
   /* Contiguous files (on the Masscomp) have to specify the size in
   /* Contiguous files (on the Masscomp) have to specify the size in
@@ -728,6 +741,9 @@ extract_file (char *file_name, int typeflag)
   size_t count;
   size_t count;
   size_t written;
   size_t written;
   int interdir_made = 0;
   int interdir_made = 0;
+  mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
+  mode_t invert_permissions =
+    0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
 
 
   /* FIXME: deal with protection issues.  */
   /* FIXME: deal with protection issues.  */
 
 
@@ -745,7 +761,7 @@ extract_file (char *file_name, int typeflag)
   else
   else
     {
     {
       do
       do
-	fd = open_output_file (file_name, typeflag);
+	fd = open_output_file (file_name, typeflag, mode ^ invert_permissions);
       while (fd < 0 && maybe_recoverable (file_name, &interdir_made));
       while (fd < 0 && maybe_recoverable (file_name, &interdir_made));
 
 
       if (fd < 0)
       if (fd < 0)
@@ -810,7 +826,7 @@ extract_file (char *file_name, int typeflag)
   if (to_command_option)
   if (to_command_option)
     sys_wait_command ();
     sys_wait_command ();
   else
   else
-    set_stat (file_name, &current_stat_info, NULL, 0,
+    set_stat (file_name, &current_stat_info, NULL, invert_permissions,
 	      (old_files_option == OVERWRITE_OLD_FILES ?
 	      (old_files_option == OVERWRITE_OLD_FILES ?
 	       UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS),
 	       UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS),
 	      typeflag);
 	      typeflag);
@@ -988,16 +1004,19 @@ extract_node (char *file_name, int typeflag)
 {
 {
   int status;
   int status;
   int interdir_made = 0;
   int interdir_made = 0;
+  mode_t mode = current_stat_info.stat.st_mode & ~ current_umask;
+  mode_t invert_permissions =
+    0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
 
 
   do
   do
-    status = mknod (file_name, current_stat_info.stat.st_mode,
+    status = mknod (file_name, mode ^ invert_permissions,
 		    current_stat_info.stat.st_rdev);
 		    current_stat_info.stat.st_rdev);
   while (status && maybe_recoverable (file_name, &interdir_made));
   while (status && maybe_recoverable (file_name, &interdir_made));
 
 
   if (status != 0)
   if (status != 0)
     mknod_error (file_name);
     mknod_error (file_name);
   else
   else
-    set_stat (file_name, &current_stat_info, NULL, 0,
+    set_stat (file_name, &current_stat_info, NULL, invert_permissions,
 	      ARCHIVED_PERMSTATUS, typeflag);
 	      ARCHIVED_PERMSTATUS, typeflag);
   return status;
   return status;
 }
 }
@@ -1009,13 +1028,16 @@ extract_fifo (char *file_name, int typeflag)
 {
 {
   int status;
   int status;
   int interdir_made = 0;
   int interdir_made = 0;
+  mode_t mode = current_stat_info.stat.st_mode & ~ current_umask;
+  mode_t invert_permissions =
+    0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
 
 
-  while ((status = mkfifo (file_name, current_stat_info.stat.st_mode)))
+  while ((status = mkfifo (file_name, mode)) != 0)
     if (!maybe_recoverable (file_name, &interdir_made))
     if (!maybe_recoverable (file_name, &interdir_made))
       break;
       break;
 
 
   if (status == 0)
   if (status == 0)
-    set_stat (file_name, &current_stat_info, NULL, 0,
+    set_stat (file_name, &current_stat_info, NULL, invert_permissions,
 	      ARCHIVED_PERMSTATUS, typeflag);
 	      ARCHIVED_PERMSTATUS, typeflag);
   else
   else
     mkfifo_error (file_name);
     mkfifo_error (file_name);