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

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