Blame SOURCES/tar-1.30-Fix-the-no-overwrite-dir-option

c70535
From 14d8fc718f0c872274b90991ee634b0cd8e1a6f0 Mon Sep 17 00:00:00 2001
c70535
From: Sergey Poznyakoff <gray@gnu.org>
c70535
Date: Sat, 8 Feb 2020 13:01:47 +0200
c70535
Subject: [PATCH] Fix the --no-overwrite-dir option
c70535
c70535
Given this option, tar failed to preserve permissions of empty directories
c70535
and to create files under directories owned by the current user that did
c70535
not have the S_IWUSR bit set.
c70535
c70535
* src/extract.c (fd_chmod): Rename to fd_i_chmod.
c70535
(fd_chmod): New function.
c70535
(safe_dir_mode): New function.
c70535
(extract_dir): Special handling for existing directories in
c70535
--no-overwrite-dir mode.
c70535
* tests/extrac23.at: New file.
c70535
* tests/Makefile.am: Add new test case.
c70535
* tests/testsuite.at: Likewise.
c70535
---
c70535
 src/extract.c      | 128 ++++++++++++++++++++++++++++++---------------
c70535
 tests/Makefile.am  |   1 +
c70535
 tests/extrac23.at  |  58 ++++++++++++++++++++
c70535
 tests/testsuite.at |   1 +
c70535
 4 files changed, 146 insertions(+), 42 deletions(-)
c70535
 create mode 100644 tests/extrac23.at
c70535
c70535
diff --git a/src/extract.c b/src/extract.c
c70535
index a4a35a57..5a38ba70 100644
c70535
--- a/src/extract.c
c70535
+++ b/src/extract.c
c70535
@@ -194,7 +194,7 @@ extr_init (void)
c70535
 
c70535
 /* Use fchmod if possible, fchmodat otherwise.  */
c70535
 static int
c70535
-fd_chmod (int fd, char const *file, mode_t mode, int atflag)
c70535
+fd_i_chmod (int fd, char const *file, mode_t mode, int atflag)
c70535
 {
c70535
   if (0 <= fd)
c70535
     {
c70535
@@ -205,6 +205,42 @@ fd_chmod (int fd, char const *file, mode_t mode, int atflag)
c70535
   return fchmodat (chdir_fd, file, mode, atflag);
c70535
 }
c70535
 
c70535
+/* A version of fd_i_chmod which gracefully handles several common error
c70535
+   conditions.  Additional argument TYPEFLAG is the type of file in tar
c70535
+   notation.
c70535
+ */
c70535
+static int
c70535
+fd_chmod(int fd, char const *file_name, int mode, int atflag, int typeflag)
c70535
+{
c70535
+  int chmod_errno = fd_i_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
c70535
+
c70535
+  /* On Solaris, chmod may fail if we don't have PRIV_ALL, because
c70535
+     setuid-root files would otherwise be a backdoor.  See
c70535
+     http://opensolaris.org/jive/thread.jspa?threadID=95826
c70535
+     (2009-09-03).  */
c70535
+  if (chmod_errno == EPERM && (mode & S_ISUID)
c70535
+      && priv_set_restore_linkdir () == 0)
c70535
+    {
c70535
+      chmod_errno = fd_i_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
c70535
+      priv_set_remove_linkdir ();
c70535
+    }
c70535
+
c70535
+  /* Linux fchmodat does not support AT_SYMLINK_NOFOLLOW, and
c70535
+     returns ENOTSUP even when operating on non-symlinks, try
c70535
+     again with the flag disabled if it does not appear to be
c70535
+     supported and if the file is not a symlink.  This
c70535
+     introduces a race, alas.  */
c70535
+  if (atflag && typeflag != SYMTYPE && ! implemented (chmod_errno))
c70535
+    chmod_errno = fd_i_chmod (fd, file_name, mode, 0) == 0 ? 0 : errno;
c70535
+
c70535
+  if (chmod_errno && (typeflag != SYMTYPE || implemented (chmod_errno)))
c70535
+    {
c70535
+      errno = chmod_errno;
c70535
+      return -1;
c70535
+    }
c70535
+  return 0;
c70535
+}
c70535
+
c70535
 /* Use fchown if possible, fchownat otherwise.  */
c70535
 static int
c70535
 fd_chown (int fd, char const *file, uid_t uid, gid_t gid, int atflag)
c70535
@@ -259,35 +295,8 @@ set_mode (char const *file_name,
c70535
 
c70535
       if (current_mode != mode)
c70535
 	{
c70535
-	  int chmod_errno =
c70535
-	    fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
c70535
-
c70535
-	  /* On Solaris, chmod may fail if we don't have PRIV_ALL, because
c70535
-	     setuid-root files would otherwise be a backdoor.  See
c70535
-	     http://opensolaris.org/jive/thread.jspa?threadID=95826
c70535
-	     (2009-09-03).  */
c70535
-	  if (chmod_errno == EPERM && (mode & S_ISUID)
c70535
-	      && priv_set_restore_linkdir () == 0)
c70535
-	    {
c70535
-	      chmod_errno =
c70535
-		fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
c70535
-	      priv_set_remove_linkdir ();
c70535
-	    }
c70535
-
c70535
-	  /* Linux fchmodat does not support AT_SYMLINK_NOFOLLOW, and
c70535
-	     returns ENOTSUP even when operating on non-symlinks, try
c70535
-	     again with the flag disabled if it does not appear to be
c70535
-	     supported and if the file is not a symlink.  This
c70535
-	     introduces a race, alas.  */
c70535
-	  if (atflag && typeflag != SYMTYPE && ! implemented (chmod_errno))
c70535
-	    chmod_errno = fd_chmod (fd, file_name, mode, 0) == 0 ? 0 : errno;
c70535
-
c70535
-	  if (chmod_errno
c70535
-	      && (typeflag != SYMTYPE || implemented (chmod_errno)))
c70535
-	    {
c70535
-	      errno = chmod_errno;
c70535
-	      chmod_error_details (file_name, mode);
c70535
-	    }
c70535
+	  if (fd_chmod (fd, file_name, mode, atflag, typeflag))
c70535
+	    chmod_error_details (file_name, mode);
c70535
 	}
c70535
     }
c70535
 }
c70535
@@ -975,6 +984,26 @@ is_directory_link (const char *file_name)
c70535
   return res;
c70535
 }
c70535
 
c70535
+/* Given struct stat of a directory (or directory member) whose ownership
c70535
+   or permissions of will be restored later, return the temporary permissions
c70535
+   for that directory, sufficiently restrictive so that in the meantime
c70535
+   processes owned by other users do not inadvertently create files under this
c70535
+   directory that inherit the wrong owner, group, or permissions from the
c70535
+   directory.
c70535
+
c70535
+   If not root, though, make the directory writeable and searchable at first,
c70535
+   so that files can be created under it.
c70535
+*/
c70535
+static inline int
c70535
+safe_dir_mode (struct stat const *st)
c70535
+{
c70535
+  return ((st->st_mode
c70535
+	   & (0 < same_owner_option || 0 < same_permissions_option
c70535
+	      ? S_IRWXU
c70535
+	      : MODE_RWX))
c70535
+	  | (we_are_root ? 0 : MODE_WXUSR));
c70535
+}
c70535
+
c70535
 /* Extractor functions for various member types */
c70535
 
c70535
 static int
c70535
@@ -1004,18 +1033,7 @@ extract_dir (char *file_name, int typeflag)
c70535
   else if (typeflag == GNUTYPE_DUMPDIR)
c70535
     skip_member ();
c70535
 
c70535
-  /* If ownership or permissions will be restored later, create the
c70535
-     directory with restrictive permissions at first, so that in the
c70535
-     meantime processes owned by other users do not inadvertently
c70535
-     create files under this directory that inherit the wrong owner,
c70535
-     group, or permissions from the directory.  If not root, though,
c70535
-     make the directory writeable and searchable at first, so that
c70535
-     files can be created under it.  */
c70535
-  mode = ((current_stat_info.stat.st_mode
c70535
-	   & (0 < same_owner_option || 0 < same_permissions_option
c70535
-	      ? S_IRWXU
c70535
-	      : MODE_RWX))
c70535
-	  | (we_are_root ? 0 : MODE_WXUSR));
c70535
+  mode = safe_dir_mode (&current_stat_info.stat);
c70535
 
c70535
   for (;;)
c70535
     {
c70535
@@ -1031,6 +1049,7 @@ extract_dir (char *file_name, int typeflag)
c70535
       if (errno == EEXIST
c70535
 	  && (interdir_made
c70535
 	      || keep_directory_symlink_option
c70535
+	      || old_files_option == NO_OVERWRITE_DIR_OLD_FILES
c70535
 	      || old_files_option == DEFAULT_OLD_FILES
c70535
 	      || old_files_option == OVERWRITE_OLD_FILES))
c70535
 	{
c70535
@@ -1051,6 +1070,31 @@ extract_dir (char *file_name, int typeflag)
c70535
 		      repair_delayed_set_stat (file_name, &st);
c70535
 		      return 0;
c70535
 		    }
c70535
+		  else if (old_files_option == NO_OVERWRITE_DIR_OLD_FILES)
c70535
+		    {
c70535
+		      /* Temporarily change the directory mode to a safe
c70535
+			 value, to be able to create files in it, should
c70535
+			 the need be.
c70535
+		      */
c70535
+		      mode = safe_dir_mode (&st);
c70535
+		      status = fd_chmod(-1, file_name, mode,
c70535
+					AT_SYMLINK_NOFOLLOW, DIRTYPE);
c70535
+		      if (status == 0)
c70535
+			{
c70535
+			  /* Store the actual directory mode, to be restored
c70535
+			     later.
c70535
+			  */
c70535
+			  current_stat_info.stat = st;
c70535
+			  current_mode = mode & ~ current_umask;
c70535
+			  current_mode_mask = MODE_RWX;
c70535
+			  atflag = AT_SYMLINK_NOFOLLOW;
c70535
+			  break;
c70535
+			}
c70535
+		      else
c70535
+			{
c70535
+			  chmod_error_details (file_name, mode);
c70535
+			}
c70535
+		    }
c70535
 		  break;
c70535
 		}
c70535
 	    }
c70535
diff --git a/tests/Makefile.am b/tests/Makefile.am
c70535
index 0369a950..31ae3460 100644
c70535
--- a/tests/Makefile.am
c70535
+++ b/tests/Makefile.am
c70535
@@ -121,6 +121,7 @@ TESTSUITE_AT = \
c70535
  extrac19.at\
c70535
  extrac20.at\
c70535
  extrac21.at\
c70535
+ extrac23.at\
c70535
  filerem01.at\
c70535
  filerem02.at\
c70535
  dirrem01.at\
c70535
diff --git a/tests/extrac23.at b/tests/extrac23.at
c70535
new file mode 100644
c70535
index 00000000..669d18b6
c70535
--- /dev/null
c70535
+++ b/tests/extrac23.at
c70535
@@ -0,0 +1,58 @@
c70535
+# Test suite for GNU tar.                             -*- Autotest -*-
c70535
+# Copyright 2020 Free Software Foundation, Inc.
c70535
+#
c70535
+# This file is part of GNU tar.
c70535
+#
c70535
+# GNU tar is free software; you can redistribute it and/or modify
c70535
+# it under the terms of the GNU General Public License as published by
c70535
+# the Free Software Foundation; either version 3 of the License, or
c70535
+# (at your option) any later version.
c70535
+#
c70535
+# GNU tar is distributed in the hope that it will be useful,
c70535
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
c70535
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
c70535
+# GNU General Public License for more details.
c70535
+#
c70535
+# You should have received a copy of the GNU General Public License
c70535
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
c70535
+AT_SETUP([--no-overwrite-dir])
c70535
+AT_KEYWORDS([extract extrac23 no-overwrite-dir])
c70535
+
c70535
+# Description: Implementation of the --no-overwrite-dir option was flawed in
c70535
+# tar versions up to 1.32.90.  This option is intended to preserve metadata
c70535
+# of existing directories.  In fact it worked only for non-empty directories.
c70535
+# Moreover, if the actual directory was owned by the user tar runs as and the
c70535
+# S_IWUSR bit was not set in its actual permissions, tar failed to create files
c70535
+# in it.
c70535
+#
c70535
+# Reported by: Michael Kaufmann <mail@michael-kaufmann.ch>
c70535
+# References: <20200207112934.Horde.anXzYhAj2CHiwUrw5CuT0G-@webmail.michael-kaufmann.ch>,
c70535
+#   https://lists.gnu.org/archive/html/bug-tar/2020-02/msg00003.html
c70535
+
c70535
+AT_TAR_CHECK([
c70535
+# Test if the directory permissions are restored properly.
c70535
+mkdir dir
c70535
+chmod 755 dir
c70535
+tar cf a.tar dir
c70535
+chmod 777 dir
c70535
+tar -xf a.tar --no-overwrite-dir
c70535
+genfile --stat=mode.777 dir
c70535
+
c70535
+# Test if temprorary permissions are set correctly to allow the owner
c70535
+# to write to the directory.
c70535
+genfile --file dir/file
c70535
+tar cf a.tar dir
c70535
+rm dir/file
c70535
+chmod 400 dir
c70535
+tar -xf a.tar --no-overwrite-dir
c70535
+genfile --stat=mode.777 dir
c70535
+chmod 700 dir
c70535
+find dir
c70535
+],
c70535
+[0],
c70535
+[777
c70535
+400
c70535
+dir
c70535
+dir/file
c70535
+])
c70535
+AT_CLEANUP
c70535
diff --git a/tests/testsuite.at b/tests/testsuite.at
c70535
index 2cc43a19..0620a3c7 100644
c70535
--- a/tests/testsuite.at
c70535
+++ b/tests/testsuite.at
c70535
@@ -343,6 +343,7 @@ m4_include([extrac19.at])
c70535
 m4_include([extrac19.at])
c70535
 m4_include([extrac20.at])
c70535
 m4_include([extrac21.at])
c70535
+m4_include([extrac23.at])
c70535
 
c70535
 m4_include([backup01.at])
c70535
 
c70535
-- 
c70535
2.37.3
c70535