From c15c42ccd1e2377945fd0414eca1a49294bff454 Mon Sep 17 00:00:00 2001
From: Sergey Poznyakoff <gray@gnu.org>
Date: Thu, 27 Dec 2018 17:48:57 +0200
Subject: 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.
---
 NEWS                |  8 ++++++-
 src/sparse.c        | 50 ++++++++++++++++++++++++++++++++++++------
 tests/Makefile.am   |  5 ++++-
 tests/sptrcreat.at  | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/sptrdiff00.at | 55 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/sptrdiff01.at | 55 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/testsuite.at  |  5 ++++-
 7 files changed, 231 insertions(+), 9 deletions(-)
 create mode 100644 tests/sptrcreat.at
 create mode 100644 tests/sptrdiff00.at
 create mode 100644 tests/sptrdiff01.at

diff --git a/src/sparse.c b/src/sparse.c
index d41c0ea..f611200 100644
--- a/src/sparse.c
+++ b/src/sparse.c
@@ -1,6 +1,6 @@
 /* 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
    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);
 	  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);
       bytes_left -= bytes_read;
@@ -464,9 +488,9 @@ sparse_extract_region (struct tar_sparse_file *file, size_t i)
 	  return false;
 	}
       set_next_block_after (blk);
+      file->dumped_size += BLOCKSIZE;
       count = blocking_write (file->fd, blk->buffer, wrbytes);
       write_size -= count;
-      file->dumped_size += count;
       mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       file->offset += count;
       if (count != wrbytes)
@@ -598,6 +622,12 @@ check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
 			     rdsize);
 	  return false;
 	}
+      else if (bytes_read == 0)
+	{
+	  report_difference (file->stat_info, _("Size differs"));
+	  return false;
+	}
+      
       if (!zero_block_p (diff_buffer, bytes_read))
 	{
 	  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;
     }
+
   return true;
 }
 
@@ -635,6 +666,7 @@ check_data_region (struct tar_sparse_file *file, size_t i)
 	  return false;
 	}
       set_next_block_after (blk);
+      file->dumped_size += BLOCKSIZE;      
       bytes_read = safe_read (file->fd, diff_buffer, rdsize);
       if (bytes_read == SAFE_READ_ERROR)
 	{
@@ -645,7 +677,11 @@ check_data_region (struct tar_sparse_file *file, size_t i)
 			     rdsize);
 	  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;
       mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       if (memcmp (blk->buffer, diff_buffer, rdsize))
@@ -1213,7 +1249,8 @@ pax_decode_header (struct tar_sparse_file *file)
       union block *blk;
       char *p;
       size_t i;
-
+      off_t start;
+      
 #define COPY_BUF(b,buf,src) do                                     \
  {                                                                 \
    char *endp = b->buffer + BLOCKSIZE;                             \
@@ -1229,7 +1266,6 @@ pax_decode_header (struct tar_sparse_file *file)
        if (src == endp)                                            \
 	 {                                                         \
 	   set_next_block_after (b);                               \
-           file->dumped_size += BLOCKSIZE;                         \
            b = find_next_block ();                                 \
            src = b->buffer;                                        \
 	   endp = b->buffer + BLOCKSIZE;                           \
@@ -1240,8 +1276,8 @@ pax_decode_header (struct tar_sparse_file *file)
    dst[-1] = 0;                                                    \
  } while (0)
 
+      start = current_block_ordinal ();
       set_next_block_after (current_header);
-      file->dumped_size += BLOCKSIZE;
       blk = find_next_block ();
       p = blk->buffer;
       COPY_BUF (blk,nbuf,p);
@@ -1278,6 +1314,8 @@ pax_decode_header (struct tar_sparse_file *file)
 	  sparse_add_map (file->stat_info, &sp);
 	}
       set_next_block_after (blk);
+
+      file->dumped_size += BLOCKSIZE * (current_block_ordinal () - start);
     }
 
   return true;
