Tobias Stoeckmann wrote:

-      size = readlinkat (parentfd, name, buffer, linklen + 1);
+      size = readlinkat (parentfd, name, buffer, linklen);

Thanks for the bug report and patch. Although that patch fixes the immediate problem, it would still have the bug that a race could cause 'tar' to create a tar image of a symlink that never existed. Also, some file systems report an incorrect st_size on symlinks so it's not wise to trust st_size.

I noticed some other problems while fixing this, and installed the attached set of patches to fix what I found. The last patch should fix the bug you reported, along with the untrustworthy st_size problem.

>From 3be1c38f48700ef5884d5d9c35fede85e2f445c3 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Mon, 13 Jul 2015 09:29:51 -0700
Subject: [PATCH 1/4] tar: pacify GCC 5.1 -Wformat-signedness

* lib/wordsplit.c (struct wordsplit_node.flags):
Now unsigned, so that 'printf ("%x", p->flags)' doesn't provoke GCC.
* src/incremen.c (read_num, dumpdir_ok):
Don't printf an int with %x or %o.
---
 lib/wordsplit.c |  2 +-
 src/incremen.c  | 18 +++++++++++-------
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/lib/wordsplit.c b/lib/wordsplit.c
index bda64d3..69db2e0 100644
--- a/lib/wordsplit.c
+++ b/lib/wordsplit.c
@@ -221,7 +221,7 @@ struct wordsplit_node
 {
   struct wordsplit_node *prev;	/* Previous element */
   struct wordsplit_node *next;	/* Next element */
-  int flags;			/* Node flags */
+  unsigned flags;		/* Node flags */
   union
   {
     struct
diff --git a/src/incremen.c b/src/incremen.c
index b1b70ba..e549bbd 100644
--- a/src/incremen.c
+++ b/src/incremen.c
@@ -735,7 +735,7 @@ scan_directory (struct tar_stat_info *st)
     savedir_error (dir);
 
   info_attach_exclist (st);
-  
+
   tmp = xstrdup (dir);
   zap_slashes (tmp);
 
@@ -1155,11 +1155,14 @@ read_num (FILE *fp, char const *fieldname,
     }
 
   if (c)
-    FATAL_ERROR ((0, 0,
-		  _("%s: byte %s: %s %s followed by invalid byte 0x%02x"),
-		  quotearg_colon (listed_incremental_option),
-		  offtostr (ftello (fp), offbuf),
-		  fieldname, buf, c));
+    {
+      unsigned uc = c;
+      FATAL_ERROR ((0, 0,
+		    _("%s: byte %s: %s %s followed by invalid byte 0x%02x"),
+		    quotearg_colon (listed_incremental_option),
+		    offtostr (ftello (fp), offbuf),
+		    fieldname, buf, uc));
+    }
 
   *pval = strtosysint (buf, NULL, min_val, max_val);
   conversion_errno = errno;
@@ -1541,9 +1544,10 @@ dumpdir_ok (char *dumpdir)
     {
       if (expect && *p != expect)
 	{
+	  unsigned char uc = *p;
 	  ERROR ((0, 0,
 		  _("Malformed dumpdir: expected '%c' but found %#3o"),
-		  expect, *p));
+		  expect, uc));
 	  return false;
 	}
       switch (*p)
-- 
2.1.0

>From d1cfcd6f70ab54fa291b57fad949e5f872bfdc36 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Mon, 13 Jul 2015 09:32:46 -0700
Subject: [PATCH 2/4] tar: port to recent gnulib

* gnulib.modules: Remove 'acl' and add 'file-has-acl'.
---
 gnulib.modules | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnulib.modules b/gnulib.modules
index 0d2e76c..ec3dc90 100644
--- a/gnulib.modules
+++ b/gnulib.modules
@@ -18,7 +18,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-acl
 alloca
 argmatch
 argp
@@ -37,6 +36,7 @@ fchownat
 fcntl-h
 fdopendir
 fdutimensat
+file-has-acl
 fileblocks
 fnmatch-gnu
 fprintftime
-- 
2.1.0

>From 88300c41f06a7f0e9cef5d1d5c0654e5ccf5fa22 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Mon, 13 Jul 2015 09:46:17 -0700
Subject: [PATCH 3/4] tar: port -d to longer symlinks

* src/compare.c (diff_symlink):
Don't use alloca on symlink length; it might be too big for the stack.
Don't assume that readlinkat's return value fits in 'int'.
Prefer memcmp to strncmp where either will do.
---
 src/compare.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/compare.c b/src/compare.c
index d29cfdd..7ed4558 100644
--- a/src/compare.c
+++ b/src/compare.c
@@ -270,11 +270,12 @@ diff_link (void)
 static void
 diff_symlink (void)
 {
+  char buf[1024];
   size_t len = strlen (current_stat_info.link_name);
-  char *linkbuf = alloca (len + 1);
+  char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
 
-  int status = readlinkat (chdir_fd, current_stat_info.file_name,
-			   linkbuf, len + 1);
+  ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
+			       linkbuf, len + 1);
 
   if (status < 0)
     {
@@ -285,8 +286,11 @@ diff_symlink (void)
       report_difference (&current_stat_info, NULL);
     }
   else if (status != len
-	   || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
+	   || memcmp (current_stat_info.link_name, linkbuf, len) != 0)
     report_difference (&current_stat_info, _("Symlink differs"));
+
+  if (linkbuf != buf)
+    free (linkbuf);
 }
 #endif
 
-- 
2.1.0

>From 1fd3cbdebddcf7f31c92b6b967b955a41737c71e Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Mon, 13 Jul 2015 09:53:00 -0700
Subject: [PATCH 4/4] tar: fix symlink race and symlink transform bug

Problem reported by Tobias Stoeckmann in:
http://lists.gnu.org/archive/html/bug-tar/2015-07/msg00004.html
* gnulib.modules: Add areadlinkat-with-size.
* src/create.c: Include areadlink.h.
(dump_file0): Use areadlinkat_with_size, rather than trying to do
it by hand, incorrectly.  This also avoids assumption that
the symlink contents fit on the stack.  Also, use the transformed
link name, not the original link name, when deciding whether the
name is long enough to require writing a long link.
---
 gnulib.modules |  1 +
 src/create.c   | 22 +++++++++-------------
 2 files changed, 10 insertions(+), 13 deletions(-)

diff --git a/gnulib.modules b/gnulib.modules
index ec3dc90..fe5ab73 100644
--- a/gnulib.modules
+++ b/gnulib.modules
@@ -19,6 +19,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 alloca
+areadlinkat-with-size
 argmatch
 argp
 argp-version-etc
diff --git a/src/create.c b/src/create.c
index 1b08e0b..7cdc978 100644
--- a/src/create.c
+++ b/src/create.c
@@ -22,6 +22,7 @@
 
 #include <system.h>
 
+#include <areadlink.h>
 #include <quotearg.h>
 
 #include "common.h"
@@ -1114,7 +1115,7 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
     return;
 
   info_attach_exclist (st);
-  
+
   if (incremental_option && archive_format != POSIX_FORMAT)
     blk->header.typeflag = GNUTYPE_DUMPDIR;
   else /* if (standard_option) */
@@ -1198,7 +1199,7 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
 	    char const *entry;
 	    size_t entry_len;
 	    size_t name_len;
-	    
+
 	    name_buf = xstrdup (st->orig_file_name);
 	    name_size = name_len = strlen (name_buf);
 
@@ -1837,22 +1838,17 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
 #ifdef HAVE_READLINK
   else if (S_ISLNK (st->stat.st_mode))
     {
-      char *buffer;
-      int size;
-      size_t linklen = st->stat.st_size;
-      if (linklen != st->stat.st_size || linklen + 1 == 0)
-	xalloc_die ();
-      buffer = (char *) alloca (linklen + 1);
-      size = readlinkat (parentfd, name, buffer, linklen + 1);
-      if (size < 0)
+      st->link_name = areadlinkat_with_size (parentfd, name, st->stat.st_size);
+      if (!st->link_name)
 	{
+	  if (errno == ENOMEM)
+	    xalloc_die ();
 	  file_removed_diag (p, top_level, readlink_diag);
 	  return;
 	}
-      buffer[size] = '\0';
-      assign_string (&st->link_name, buffer);
       transform_name (&st->link_name, XFORM_SYMLINK);
-      if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size)
+      if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT)
+	  < strlen (st->link_name))
 	write_long_link (st);
 
       xattrs_selinux_get (parentfd, name, st, 0);
-- 
2.1.0

Reply via email to