On 8/25/22 07:23, Lukas Javorsky wrote:
[1] https://www.mail-archive.com/bug-tar@gnu.org/msg05904.html
[2] https://bugzilla.redhat.com/show_bug.cgi?id=1886540

Thanks for reminding me about that. Although that patch helps, I don't think it's a complete fix. I installed the attached instead; please give these patches a try. The first one's more important.

It seems odd that we must use mknodat here. Why can't we create the file using openat with O_CREAT|O_TRUNC|O_EXCL instead? That would be simpler. Is it some weird limitation of xattr? Perhaps should be a comment if so.
From 0b74885e81b90d6ab4890b195dce99ca9109fe59 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Fri, 26 Aug 2022 15:23:23 -0500
Subject: [PATCH 1/2] Fix bug with -x --xattr read-only files
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem reported by Kevin Raymond in:
https://bugzilla.redhat.com/show_bug.cgi?id=1886540
* src/extract.c (open_output_file): If we already created the
empty file, do not open with O_EXCL, or with O_CREAT or O_TRUNC
for that matter.  Instead, use only O_NOFOLLOW to avoid some
races.  When estimating current mode, use openflag & O_EXCL rather
than overwriting_old_files.
(extract_file): Also invert S_IWUSR if it’s not set.
* tests/xattr08.at: New test.
* tests/Makefile.am, tests/testsuite.at: Add it.
---
 src/extract.c      | 31 +++++++++++++++----------------
 tests/Makefile.am  |  1 +
 tests/testsuite.at |  1 +
 tests/xattr08.at   | 41 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 58 insertions(+), 16 deletions(-)
 create mode 100644 tests/xattr08.at

diff --git a/src/extract.c b/src/extract.c
index 2813c961..c1f5731b 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -1189,14 +1189,12 @@ open_output_file (char const *file_name, int typeflag, mode_t mode,
   int fd;
   bool overwriting_old_files = old_files_option == OVERWRITE_OLD_FILES;
   int openflag = (O_WRONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
-		  | O_CREAT
-		  | (overwriting_old_files
-		     ? O_TRUNC | (dereference_option ? 0 : O_NOFOLLOW)
-		     : O_EXCL));
-
-  /* File might be created in set_xattr. So clear O_EXCL to avoid open() fail */
-  if (file_created)
-    openflag = openflag & ~O_EXCL;
+		  | (file_created
+		     ? O_NOFOLLOW
+		     : (O_CREAT
+			| (overwriting_old_files
+			   ? O_TRUNC | (dereference_option ? 0 : O_NOFOLLOW)
+			   : O_EXCL))));
 
   if (typeflag == CONTTYPE)
     {
@@ -1227,7 +1225,12 @@ open_output_file (char const *file_name, int typeflag, mode_t mode,
   fd = openat (chdir_fd, file_name, openflag, mode);
   if (0 <= fd)
     {
-      if (overwriting_old_files)
+      if (openflag & O_EXCL)
+	{
+	  *current_mode = mode & ~ current_umask;
+	  *current_mode_mask = MODE_RWX;
+	}
+      else
 	{
 	  struct stat st;
 	  if (fstat (fd, &st) != 0)
@@ -1246,11 +1249,6 @@ open_output_file (char const *file_name, int typeflag, mode_t mode,
 	  *current_mode = st.st_mode;
 	  *current_mode_mask = ALL_MODE_BITS;
 	}
-      else
-	{
-	  *current_mode = mode & ~ current_umask;
-	  *current_mode_mask = MODE_RWX;
-	}
     }
 
   return fd;
@@ -1268,8 +1266,9 @@ extract_file (char *file_name, int typeflag)
   bool interdir_made = false;
   mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
 		 & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
-  mode_t invert_permissions = 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO)
-                                                    : 0;
+  mode_t invert_permissions
+    = ((0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0)
+       | ((mode & S_IWUSR) ^ S_IWUSR));
   mode_t current_mode = 0;
   mode_t current_mode_mask = 0;
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index eff8a3bf..a0ce690b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -275,6 +275,7 @@ TESTSUITE_AT = \
  xattr05.at\
  xattr06.at\
  xattr07.at\
+ xattr08.at\
  acls01.at\
  acls02.at\
  acls03.at\
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 0769e71b..a99cdeee 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -502,6 +502,7 @@ m4_include([xattr04.at])
 m4_include([xattr05.at])
 m4_include([xattr06.at])
 m4_include([xattr07.at])
+m4_include([xattr08.at])
 
 m4_include([acls01.at])
 m4_include([acls02.at])
diff --git a/tests/xattr08.at b/tests/xattr08.at
new file mode 100644
index 00000000..2beef235
--- /dev/null
+++ b/tests/xattr08.at
@@ -0,0 +1,41 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2022 Free Software Foundation, Inc.
+
+# This file is part of GNU tar.
+
+# GNU tar is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU tar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Test description:
+# Test read-only files can be extracted with --xattr.
+# Per report:
+# https://lists.gnu.org/r/bug-tar/2020-10/msg00001.html
+
+AT_SETUP([xattrs: xattrs and read-only files])
+AT_KEYWORDS([xattrs xattr08])
+
+AT_TAR_CHECK([
+AT_XATTRS_PREREQ
+mkdir dir dir2
+genfile --file dir/file
+
+setfattr -n user.test -v OurDirValue dir/file
+chmod a-w dir/file
+
+tar --xattrs -C dir -cf archive.tar file
+tar --xattrs -C dir2 -xf archive.tar
+])
+
+AT_CLEANUP
-- 
2.37.2

From 35d9845d5d410237e418113ec79d8f9e4b0b2bb8 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Fri, 26 Aug 2022 16:38:29 -0500
Subject: [PATCH 2/2] Do not diagnose same xattr file twice

* src/extract.c (set_xattr): Simplify, by having it do only
the mknodat and xattrs_xattrs_set, rather than also
trying to recover from failure.  Caller simplified too.
* tests/xattr07.at (xattrs: xattrs and --skip-old-files):
Adjust test to match fixed behavior.
---
 src/extract.c    | 59 ++++++++++++++----------------------------------
 tests/xattr07.at |  2 --
 2 files changed, 17 insertions(+), 44 deletions(-)

diff --git a/src/extract.c b/src/extract.c
index c1f5731b..7696d5e4 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -903,42 +903,21 @@ maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
    in advance dramatically improves the following  performance of reading and
    writing a file).  If not restoring permissions, invert the INVERT_PERMISSIONS
    bits from the file's current permissions.  TYPEFLAG specifies the type of the
-   file.  Returns non-zero when error occurs (while un-available xattrs is not
-   an error, rather no-op).  Non-zero FILE_CREATED indicates set_xattr has
-   created the file. */
+   file.  Return a negative number (setting errno) on failure, zero if
+   successful but FILE_NAME was not created (e.g., xattrs not
+   available), and a positive number if FILE_NAME was created.  */
 static int
 set_xattr (char const *file_name, struct tar_stat_info const *st,
-           mode_t invert_permissions, char typeflag, int *file_created)
+           mode_t mode, char typeflag)
 {
 #ifdef HAVE_XATTRS
-  bool interdir_made = false;
-
   if ((xattrs_option > 0) && st->xattr_map.xm_size)
     {
-      mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
-
-      for (;;)
-        {
-          if (!mknodat (chdir_fd, file_name, mode ^ invert_permissions, 0))
-            {
-              /* Successfully created file */
-              xattrs_xattrs_set (st, file_name, typeflag, 0);
-              *file_created = 1;
-              return 0;
-            }
-
-          switch (maybe_recoverable ((char *)file_name, false, &interdir_made))
-            {
-              case RECOVER_OK:
-                continue;
-              case RECOVER_NO:
-                skip_member ();
-                open_error (file_name);
-                return 1;
-              case RECOVER_SKIP:
-                return 0;
-            }
-        }
+      int r = mknodat (chdir_fd, file_name, mode, 0);
+      if (r < 0)
+	return r;
+      xattrs_xattrs_set (st, file_name, typeflag, 0);
+      return 1;
     }
 #endif
 
@@ -1266,9 +1245,6 @@ extract_file (char *file_name, int typeflag)
   bool interdir_made = false;
   mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
 		 & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
-  mode_t invert_permissions
-    = ((0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0)
-       | ((mode & S_IWUSR) ^ S_IWUSR));
   mode_t current_mode = 0;
   mode_t current_mode_mask = 0;
 
@@ -1285,15 +1261,14 @@ extract_file (char *file_name, int typeflag)
     }
   else
     {
-      int file_created = 0;
-      if (set_xattr (file_name, &current_stat_info, invert_permissions,
-                     typeflag, &file_created))
-        return 1;
-
-      while ((fd = open_output_file (file_name, typeflag, mode,
-                                     file_created, &current_mode,
-                                     &current_mode_mask))
-	     < 0)
+      int file_created;
+      while (((file_created = set_xattr (file_name, &current_stat_info,
+					 mode | S_IWUSR, typeflag))
+	      < 0)
+	     || ((fd = open_output_file (file_name, typeflag, mode,
+					 file_created, &current_mode,
+					 &current_mode_mask))
+		 < 0))
 	{
 	  int recover = maybe_recoverable (file_name, true, &interdir_made);
 	  if (recover != RECOVER_OK)
diff --git a/tests/xattr07.at b/tests/xattr07.at
index dcc3aabe..fa6797d8 100644
--- a/tests/xattr07.at
+++ b/tests/xattr07.at
@@ -62,8 +62,6 @@ user.test="OurFileValue2"
 user.test="OurFileValue2"
 ], [tar: dir: skipping existing file
 tar: dir/file: skipping existing file
-tar: dir/file: skipping existing file
-tar: dir/file2: skipping existing file
 tar: dir/file2: skipping existing file
 tar: dir/file: Cannot open: File exists
 tar: dir/file2: Cannot open: File exists
-- 
2.37.2

Reply via email to