2005-05-11  Dmitry V. Levin <ldv@altlinux.org>

	Fix race conditions in code which creates regular files, creates
	directories and devices or sets its permissions, in copy-in and
	copy-pass modes.

	* src/copyin.c (try_existing_file): Add argument: existing_mode.
	When `*existing_dir' is set to true, store file mode to
	`*existing_mode'.
	(create_final_defers, copyin_regular_file): Change open(2)
	calls to use O_EXCL.
	(copyin_directory): Change mkdir(2) and mknod(2) calls to use
	safe permissions.
	Add argument: existing_mode.
	When reusing already existing directory, no_chown_flag is not
	set and directory mode is too permissive, chmod it to safe mode
	before chown(2) call.
	(copyin_file): Pass existing_mode from try_existing_file()
	to copyin_directory().
	* src/copypass.c (process_copy_pass): Change open(2) calls to
	use O_EXCL.
	Change mkdir(2) and mknod(2) calls to use safe permissions.
	When reusing already existing directory, no_chown_flag is not
	set and directory mode is too permissive, chmod it to safe mode
	before chown(2) call.

diff -uprk.orig cpio-2.6.orig/src/copyin.c cpio-2.6/src/copyin.c
--- cpio-2.6.orig/src/copyin.c	2005-05-11 12:20:17 +0000
+++ cpio-2.6/src/copyin.c	2005-05-11 13:39:03 +0000
@@ -185,11 +185,12 @@ list_file(struct new_cpio_header* file_h
 
 static int
 try_existing_file(struct new_cpio_header* file_hdr, int in_file_des,
-		  int *existing_dir)
+		  int *existing_dir, mode_t *existing_mode)
 {
   struct stat file_stat;
 
   *existing_dir = false;
+  *existing_mode = 0;
   if (lstat (file_hdr->c_name, &file_stat) == 0)
     {
       if (S_ISDIR (file_stat.st_mode)
@@ -199,6 +200,7 @@ try_existing_file(struct new_cpio_header
 	     we are trying to create, don't complain about
 	     it.  */
 	  *existing_dir = true;
+	  *existing_mode = file_stat.st_mode;
 	  return 0;
 	}
       else if (!unconditional_flag
@@ -375,12 +377,12 @@ create_final_defers ()
 	  continue;
 	}
       out_file_des = open (d->header.c_name,
-			   O_CREAT | O_WRONLY | O_BINARY, 0600);
+			   O_CREAT | O_EXCL | O_WRONLY | O_BINARY, 0600);
       if (out_file_des < 0 && create_dir_flag)
 	{
 	  create_all_directories (d->header.c_name);
 	  out_file_des = open (d->header.c_name,
-			       O_CREAT | O_WRONLY | O_BINARY,
+			       O_CREAT | O_EXCL | O_WRONLY | O_BINARY,
 			       0600);
 	}
       if (out_file_des < 0)
@@ -497,13 +499,13 @@ copyin_regular_file (struct new_cpio_hea
     
       /* If not linked, copy the contents of the file.  */
       out_file_des = open (file_hdr->c_name,
-			   O_CREAT | O_WRONLY | O_BINARY, 0600);
+			   O_CREAT | O_EXCL | O_WRONLY | O_BINARY, 0600);
   
       if (out_file_des < 0 && create_dir_flag)
 	{
 	  create_all_directories (file_hdr->c_name);
 	  out_file_des = open (file_hdr->c_name,
-			       O_CREAT | O_WRONLY | O_BINARY,
+			       O_CREAT | O_EXCL | O_WRONLY | O_BINARY,
 			       0600);
 	}
       
@@ -604,7 +606,8 @@ copyin_regular_file (struct new_cpio_hea
 }
 
 static void
-copyin_directory(struct new_cpio_header* file_hdr, int existing_dir)
+copyin_directory (struct new_cpio_header *file_hdr, int existing_dir,
+		  mode_t existing_mode)
 {
   int res;			/* Result of various function calls.  */
 #ifdef HPUX_CDF
@@ -647,14 +650,22 @@ copyin_directory(struct new_cpio_header*
 	  cdf_flag = 1;
 	}
 #endif
-      res = mkdir (file_hdr->c_name, file_hdr->c_mode);
+      res = mkdir (file_hdr->c_name, file_hdr->c_mode & ~077);
     }
   else
-    res = 0;
+    {
+      if (!no_chown_flag && (existing_mode & 077) != 0
+	  && chmod (file_hdr->c_name, existing_mode & 07700) < 0)
+	{
+	  error (0, errno, "%s: chmod", file_hdr->c_name);
+	  return;
+	}
+      res = 0;
+    }
   if (res < 0 && create_dir_flag)
     {
       create_all_directories (file_hdr->c_name);
-      res = mkdir (file_hdr->c_name, file_hdr->c_mode);
+      res = mkdir (file_hdr->c_name, file_hdr->c_mode & ~077);
     }
   if (res < 0)
     {
@@ -743,12 +754,12 @@ copyin_device(struct new_cpio_header* fi
       return;
     }
   
-  res = mknod (file_hdr->c_name, file_hdr->c_mode,
+  res = mknod (file_hdr->c_name, file_hdr->c_mode & ~077,
 	    makedev (file_hdr->c_rdev_maj, file_hdr->c_rdev_min));
   if (res < 0 && create_dir_flag)
     {
       create_all_directories (file_hdr->c_name);
-      res = mknod (file_hdr->c_name, file_hdr->c_mode,
+      res = mknod (file_hdr->c_name, file_hdr->c_mode & ~077,
 	    makedev (file_hdr->c_rdev_maj, file_hdr->c_rdev_min));
     }
   if (res < 0)
@@ -827,9 +838,11 @@ static void
 copyin_file (struct new_cpio_header* file_hdr, int in_file_des)
 {
   int existing_dir;
+  mode_t existing_mode;
 
   if (!to_stdout_option
-      && try_existing_file (file_hdr, in_file_des, &existing_dir) < 0)
+      && try_existing_file (file_hdr, in_file_des, &existing_dir,
+			    &existing_mode) < 0)
     return;
 
   /* Do the real copy or link.  */
@@ -840,7 +853,7 @@ copyin_file (struct new_cpio_header* fil
       break;
 
     case CP_IFDIR:
-      copyin_directory(file_hdr, existing_dir);
+      copyin_directory(file_hdr, existing_dir, existing_mode);
       break;
 
     case CP_IFCHR:
diff -uprk.orig cpio-2.6.orig/src/copypass.c cpio-2.6/src/copypass.c
--- cpio-2.6.orig/src/copypass.c	2005-05-11 12:20:17 +0000
+++ cpio-2.6/src/copypass.c	2005-05-11 13:44:28 +0000
@@ -157,12 +157,12 @@ process_copy_pass ()
 		  continue;
 		}
 	      out_file_des = open (output_name.ds_string,
-				   O_CREAT | O_WRONLY | O_BINARY, 0600);
+				   O_CREAT | O_EXCL | O_WRONLY | O_BINARY, 0600);
 	      if (out_file_des < 0 && create_dir_flag)
 		{
 		  create_all_directories (output_name.ds_string);
 		  out_file_des = open (output_name.ds_string,
-				       O_CREAT | O_WRONLY | O_BINARY, 0600);
+				       O_CREAT | O_EXCL | O_WRONLY | O_BINARY, 0600);
 		}
 	      if (out_file_des < 0)
 		{
@@ -243,15 +243,23 @@ process_copy_pass ()
 		  cdf_flag = 1;
 		}
 #endif
-	      res = mkdir (output_name.ds_string, in_file_stat.st_mode);
+	      res = mkdir (output_name.ds_string, in_file_stat.st_mode & ~077);
 
 	    }
 	  else
-	    res = 0;
+	    {
+	      if (!no_chown_flag && (out_file_stat.st_mode & 077) != 0
+		  && chmod (output_name.ds_string, out_file_stat.st_mode & 07700) < 0)
+		{
+		  error (0, errno, "%s: chmod", output_name.ds_string);
+		  continue;
+		}
+	      res = 0;
+	    }
 	  if (res < 0 && create_dir_flag)
 	    {
 	      create_all_directories (output_name.ds_string);
-	      res = mkdir (output_name.ds_string, in_file_stat.st_mode);
+	      res = mkdir (output_name.ds_string, in_file_stat.st_mode & ~077);
 	    }
 	  if (res < 0)
 	    {
@@ -314,12 +322,12 @@ process_copy_pass ()
 
 	  if (link_res < 0)
 	    {
-	      res = mknod (output_name.ds_string, in_file_stat.st_mode,
+	      res = mknod (output_name.ds_string, in_file_stat.st_mode & ~077,
 			   in_file_stat.st_rdev);
 	      if (res < 0 && create_dir_flag)
 		{
 		  create_all_directories (output_name.ds_string);
-		  res = mknod (output_name.ds_string, in_file_stat.st_mode,
+		  res = mknod (output_name.ds_string, in_file_stat.st_mode & ~077,
 			       in_file_stat.st_rdev);
 		}
 	      if (res < 0)
