4
0
Эх сурвалжийг харах

Fix CVE-2018-20482

* NEWS: Update.
* src/sparse.c (sparse_dump_region): Handle short read condition.
(sparse_extract_region,check_data_region): Fix dumped_size calculation.
Handle short read condition.
(pax_decode_header): Fix dumped_size calculation.
* tests/Makefile.am: Add new testcases.
* tests/testsuite.at: Likewise.

* tests/sptrcreat.at: New file.
* tests/sptrdiff00.at: New file.
* tests/sptrdiff01.at: New file.
Sergey Poznyakoff 6 жил өмнө
parent
commit
c15c42ccd1

+ 7 - 1
NEWS

@@ -1,4 +1,4 @@
-GNU tar NEWS - User visible changes. 2018-12-21
+GNU tar NEWS - User visible changes. 2018-12-27
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 
 
 
 
@@ -25,6 +25,12 @@ semantics of the option.
 Previous versions of tar extracted NAME, those of named members that
 Previous versions of tar extracted NAME, those of named members that
 appeared before it, and everything after it.
 appeared before it, and everything after it.
 
 
+* Fix CVE-2018-20482
+
+When creating archives with the --sparse option, previous versions of
+tar would loop endlessly if a sparse file had been truncated while
+being archived.
+
 
 
 version 1.30 - Sergey Poznyakoff, 2017-12-17
 version 1.30 - Sergey Poznyakoff, 2017-12-17
 
 

+ 44 - 6
src/sparse.c

@@ -1,6 +1,6 @@
 /* Functions for dealing with sparse files
 /* Functions for dealing with sparse files
 
 
-   Copyright 2003-2007, 2010, 2013-2017 Free Software Foundation, Inc.
+   Copyright 2003-2007, 2010, 2013-2018 Free Software Foundation, Inc.
 
 
    This program is free software; you can redistribute it and/or modify it
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
    under the terms of the GNU General Public License as published by the
@@ -427,6 +427,30 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i)
 			     bufsize);
 			     bufsize);
 	  return false;
 	  return false;
 	}
 	}
+      else if (bytes_read == 0)
+	{
+	  char buf[UINTMAX_STRSIZE_BOUND];
+	  struct stat st;
+	  size_t n;
+	  if (fstat (file->fd, &st) == 0)
+	    n = file->stat_info->stat.st_size - st.st_size;
+	  else
+	    n = file->stat_info->stat.st_size
+	      - (file->stat_info->sparse_map[i].offset
+		 + file->stat_info->sparse_map[i].numbytes
+		 - bytes_left);
+	  
+	  WARNOPT (WARN_FILE_SHRANK,
+		   (0, 0,
+		    ngettext ("%s: File shrank by %s byte; padding with zeros",
+			      "%s: File shrank by %s bytes; padding with zeros",
+			      n),
+		    quotearg_colon (file->stat_info->orig_file_name),
+		    STRINGIFY_BIGINT (n, buf)));
+	  if (! ignore_failed_read_option)
+	    set_exit_status (TAREXIT_DIFFERS);
+	  return false;
+	}
 
 
       memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read);
       memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read);
       bytes_left -= bytes_read;
       bytes_left -= bytes_read;
@@ -464,9 +488,9 @@ sparse_extract_region (struct tar_sparse_file *file, size_t i)
 	  return false;
 	  return false;
 	}
 	}
       set_next_block_after (blk);
       set_next_block_after (blk);
+      file->dumped_size += BLOCKSIZE;
       count = blocking_write (file->fd, blk->buffer, wrbytes);
       count = blocking_write (file->fd, blk->buffer, wrbytes);
       write_size -= count;
       write_size -= count;
-      file->dumped_size += count;
       mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       file->offset += count;
       file->offset += count;
       if (count != wrbytes)
       if (count != wrbytes)
@@ -598,6 +622,12 @@ check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
 			     rdsize);
 			     rdsize);
 	  return false;
 	  return false;
 	}
 	}
+      else if (bytes_read == 0)
+	{
+	  report_difference (file->stat_info, _("Size differs"));
+	  return false;
+	}
+      
       if (!zero_block_p (diff_buffer, bytes_read))
       if (!zero_block_p (diff_buffer, bytes_read))
 	{
 	{
 	  char begbuf[INT_BUFSIZE_BOUND (off_t)];
 	  char begbuf[INT_BUFSIZE_BOUND (off_t)];
@@ -609,6 +639,7 @@ check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
 
 
       beg += bytes_read;
       beg += bytes_read;
     }
     }
+
   return true;
   return true;
 }
 }
 
 
@@ -635,6 +666,7 @@ check_data_region (struct tar_sparse_file *file, size_t i)
 	  return false;
 	  return false;
 	}
 	}
       set_next_block_after (blk);
       set_next_block_after (blk);
+      file->dumped_size += BLOCKSIZE;      
       bytes_read = safe_read (file->fd, diff_buffer, rdsize);
       bytes_read = safe_read (file->fd, diff_buffer, rdsize);
       if (bytes_read == SAFE_READ_ERROR)
       if (bytes_read == SAFE_READ_ERROR)
 	{
 	{
@@ -645,7 +677,11 @@ check_data_region (struct tar_sparse_file *file, size_t i)
 			     rdsize);
 			     rdsize);
 	  return false;
 	  return false;
 	}
 	}
-      file->dumped_size += bytes_read;
+      else if (bytes_read == 0)
+	{
+	  report_difference (&current_stat_info, _("Size differs"));
+	  return false;
+	}
       size_left -= bytes_read;
       size_left -= bytes_read;
       mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       if (memcmp (blk->buffer, diff_buffer, rdsize))
       if (memcmp (blk->buffer, diff_buffer, rdsize))
@@ -1213,7 +1249,8 @@ pax_decode_header (struct tar_sparse_file *file)
       union block *blk;
       union block *blk;
       char *p;
       char *p;
       size_t i;
       size_t i;
-
+      off_t start;
+      
 #define COPY_BUF(b,buf,src) do                                     \
 #define COPY_BUF(b,buf,src) do                                     \
  {                                                                 \
  {                                                                 \
    char *endp = b->buffer + BLOCKSIZE;                             \
    char *endp = b->buffer + BLOCKSIZE;                             \
@@ -1229,7 +1266,6 @@ pax_decode_header (struct tar_sparse_file *file)
        if (src == endp)                                            \
        if (src == endp)                                            \
 	 {                                                         \
 	 {                                                         \
 	   set_next_block_after (b);                               \
 	   set_next_block_after (b);                               \
-           file->dumped_size += BLOCKSIZE;                         \
            b = find_next_block ();                                 \
            b = find_next_block ();                                 \
            src = b->buffer;                                        \
            src = b->buffer;                                        \
 	   endp = b->buffer + BLOCKSIZE;                           \
 	   endp = b->buffer + BLOCKSIZE;                           \
@@ -1240,8 +1276,8 @@ pax_decode_header (struct tar_sparse_file *file)
    dst[-1] = 0;                                                    \
    dst[-1] = 0;                                                    \
  } while (0)
  } while (0)
 
 
+      start = current_block_ordinal ();
       set_next_block_after (current_header);
       set_next_block_after (current_header);
-      file->dumped_size += BLOCKSIZE;
       blk = find_next_block ();
       blk = find_next_block ();
       p = blk->buffer;
       p = blk->buffer;
       COPY_BUF (blk,nbuf,p);
       COPY_BUF (blk,nbuf,p);
@@ -1278,6 +1314,8 @@ pax_decode_header (struct tar_sparse_file *file)
 	  sparse_add_map (file->stat_info, &sp);
 	  sparse_add_map (file->stat_info, &sp);
 	}
 	}
       set_next_block_after (blk);
       set_next_block_after (blk);
+
+      file->dumped_size += BLOCKSIZE * (current_block_ordinal () - start);
     }
     }
 
 
   return true;
   return true;

+ 4 - 1
tests/Makefile.am

@@ -1,6 +1,6 @@
 # Makefile for GNU tar regression tests.
 # Makefile for GNU tar regression tests.
 
 
-# Copyright 1996-1997, 1999-2001, 2003-2007, 2009, 2012-2015 Free Software
+# Copyright 1996-1997, 1999-2001, 2003-2007, 2009, 2012-2018 Free Software
 
 
 # This file is part of GNU tar.
 # This file is part of GNU tar.
 
 
@@ -238,6 +238,9 @@ TESTSUITE_AT = \
  spmvp00.at\
  spmvp00.at\
  spmvp01.at\
  spmvp01.at\
  spmvp10.at\
  spmvp10.at\
+ sptrcreat.at\
+ sptrdiff00.at\
+ sptrdiff01.at\
  time01.at\
  time01.at\
  time02.at\
  time02.at\
  truncate.at\
  truncate.at\

+ 62 - 0
tests/sptrcreat.at

@@ -0,0 +1,62 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright 2018 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/>.
+
+# Tar up to 1.30 would loop endlessly if a sparse file had been truncated
+# while being archived (with --sparse flag).
+#
+# The bug has been assigned id CVE-2018-20482 (on the grounds that it is a
+# denial of service possibility).
+# 
+# Reported by: Chris Siebenmann <cks.gnutar-01@cs.toronto.edu>
+# References: <20181226223948.781EB32008E@apps1.cs.toronto.edu>,
+#   <http://lists.gnu.org/archive/html/bug-tar/2018-12/msg00023.html>
+#   <https://utcc.utoronto.ca/~cks/space/blog/sysadmin/TarFindingTruncateBug>
+#   <https://nvd.nist.gov/vuln/detail/CVE-2018-20482>
+
+AT_SETUP([sparse file truncated while archiving])
+AT_KEYWORDS([truncate filechange sparse sptr sptrcreat])
+
+AT_TAR_CHECK([
+genfile --sparse --block-size=1024 --file foo \
+  0 ABCDEFGHIJ 1M ABCDEFGHIJ 10M ABCDEFGHIJ 200M ABCDEFGHIJ
+genfile --file baz
+genfile --run --checkpoint 3 --length 200m --truncate foo -- \
+ tar --checkpoint=1 \
+     --checkpoint-action=echo \
+     --checkpoint-action=sleep=1 \
+     --sparse -vcf bar foo baz
+echo Exit status: $?
+echo separator
+genfile --file foo --seek 200m --length 11575296 --pattern=zeros
+tar dvf bar],
+[1],
+[foo
+baz
+Exit status: 1
+separator
+foo
+foo: Mod time differs
+baz
+],
+[tar: foo: File shrank by 11575296 bytes; padding with zeros
+],
+[],[],[posix, gnu, oldgnu])
+
+AT_CLEANUP

+ 55 - 0
tests/sptrdiff00.at

@@ -0,0 +1,55 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2018 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/>.
+
+# While fixing CVE-2018-20482 (see sptrcreat.at) it has been discovered
+# that similar bug exists in file checking code (tar d). 
+# This test case checks if tar correctly handles a short read condition
+# appearing in check_sparse_region.
+
+AT_SETUP([file truncated in sparse region while comparing])
+AT_KEYWORDS([truncate filechange sparse sptr sptrdiff diff])
+
+# This triggers short read in check_sparse_region.
+AT_TAR_CHECK([
+genfile --sparse --block-size=1024 --file foo \
+  0 ABCDEFGHIJ 1M ABCDEFGHIJ 10M ABCDEFGHIJ 200M ABCDEFGHIJ
+genfile --file baz
+echo creating
+tar --sparse -vcf bar foo baz
+echo comparing
+genfile --run --checkpoint 3 --length 200m --truncate foo -- \
+ tar --checkpoint=1 \
+     --checkpoint-action=echo='Write checkpoint %u' \
+     --checkpoint-action=sleep=1 \
+     --sparse -vdf bar 
+],
+[1],
+[creating
+foo
+baz
+comparing
+foo
+foo: Size differs
+baz
+],
+[],
+[],[],[posix, gnu, oldgnu])
+
+AT_CLEANUP

+ 55 - 0
tests/sptrdiff01.at

@@ -0,0 +1,55 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2018 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/>.
+
+# While fixing CVE-2018-20482 (see sptrcreat.at) it has been discovered
+# that similar bug exists in file checking code (tar d). 
+# This test case checks if tar correctly handles a short read condition
+# appearing in check_data_region.
+
+AT_SETUP([file truncated in data region while comparing])
+AT_KEYWORDS([truncate filechange sparse sptr sptrdiff diff])
+
+# This triggers short read in check_data_region.
+AT_TAR_CHECK([
+genfile --sparse --block-size=1024 --file foo \
+  0 ABCDEFGHIJ 1M ABCDEFGHIJ 10M ABCDEFGHIJ 200M ABCDEFGHIJ
+genfile --file baz
+echo creating
+tar --sparse -vcf bar foo baz
+echo comparing
+genfile --run --checkpoint 5 --length 221278210 --truncate foo -- \
+ tar --checkpoint=1 \
+     --checkpoint-action=echo='Write checkpoint %u' \
+     --checkpoint-action=sleep=1 \
+     --sparse -vdf bar 
+],
+[1],
+[creating
+foo
+baz
+comparing
+foo
+foo: Size differs
+baz
+],
+[],
+[],[],[posix, gnu, oldgnu])
+
+AT_CLEANUP

+ 4 - 1
tests/testsuite.at

@@ -1,7 +1,7 @@
 # Process this file with autom4te to create testsuite. -*- Autotest -*-
 # Process this file with autom4te to create testsuite. -*- Autotest -*-
 
 
 # Test suite for GNU tar.
 # Test suite for GNU tar.
-# Copyright 2004-2008, 2010-2017 Free Software Foundation, Inc.
+# Copyright 2004-2008, 2010-2018 Free Software Foundation, Inc.
 
 
 # This file is part of GNU tar.
 # This file is part of GNU tar.
 
 
@@ -416,6 +416,9 @@ m4_include([sparsemv.at])
 m4_include([spmvp00.at])
 m4_include([spmvp00.at])
 m4_include([spmvp01.at])
 m4_include([spmvp01.at])
 m4_include([spmvp10.at])
 m4_include([spmvp10.at])
+m4_include([sptrcreat.at])
+m4_include([sptrdiff00.at])
+m4_include([sptrdiff01.at])
 
 
 AT_BANNER([Updates])
 AT_BANNER([Updates])
 m4_include([update.at])
 m4_include([update.at])