>From c008273c7488e47dbf942ec79e83e95d34ff873f Mon Sep 17 00:00:00 2001 From: Pavel Raiskup Date: Sun, 16 Mar 2014 22:26:33 +0100 Subject: [PATCH] tar: fix infinite loop in --diff for sparse files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When sparse file got truncated, tar went into infinite loop (reliably when the file ended by hole originally, unlikely when file ended with data). Fixed also potential loop (in similar fashion) in sparse files dumping. Original bugreport: http://lists.gnu.org/archive/html/bug-tar/2014-03/msg00052.html Thanks François Ouellet * src/sparse.c (check_sparse_region): Return false when safe_read returns 0. Also print the error on correct position in file. (check_data_region): Report error when safe_read returns 0. (sparse_dump_region): Fail if file got truncated. * THANKS: Adjust. --- THANKS | 1 + src/sparse.c | 48 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/THANKS b/THANKS index b4c5427..e74f71c 100644 --- a/THANKS +++ b/THANKS @@ -175,6 +175,7 @@ Fabio d'Alessi address@hidden Frank Heckenbach address@hidden Frank Koenen address@hidden Franz-Werner Gergen address@hidden +François Ouellet address@hidden François Pinard address@hidden Fritz Elfert address@hidden George Chyu address@hidden diff --git a/src/sparse.c b/src/sparse.c index 6a97676..53c1868 100644 --- a/src/sparse.c +++ b/src/sparse.c @@ -301,6 +301,7 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i) { union block *blk; off_t bytes_left = file->stat_info->sparse_map[i].numbytes; + const char *file_name = file->stat_info->orig_file_name; if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset)) return false; @@ -314,13 +315,23 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i) bytes_read = safe_read (file->fd, blk->buffer, bufsize); if (bytes_read == SAFE_READ_ERROR) { - read_diag_details (file->stat_info->orig_file_name, + read_diag_details (file_name, (file->stat_info->sparse_map[i].offset + file->stat_info->sparse_map[i].numbytes - bytes_left), bufsize); return false; } + if (bytes_read == 0) + { + char buf[UINTMAX_STRSIZE_BOUND]; + FATAL_ERROR ((0, 0, + ngettext ("%s: File shrank by %s byte", + "%s: File shrank by %s bytes", + bytes_left), + quotearg_colon (file_name), + offtostr (bytes_left, buf))); + } memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read); bytes_left -= bytes_read; @@ -475,33 +486,37 @@ sparse_skip_file (struct tar_stat_info *st) static bool check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end) { - if (!lseek_or_error (file, beg)) + off_t offset = beg; + + if (!lseek_or_error (file, offset)) return false; - while (beg < end) + while (offset < end) { size_t bytes_read; - size_t rdsize = BLOCKSIZE < end - beg ? BLOCKSIZE : end - beg; + size_t rdsize = BLOCKSIZE < end - offset ? BLOCKSIZE : end - offset; char diff_buffer[BLOCKSIZE]; bytes_read = safe_read (file->fd, diff_buffer, rdsize); if (bytes_read == SAFE_READ_ERROR) { read_diag_details (file->stat_info->orig_file_name, - beg, - rdsize); - return false; - } - if (!zero_block_p (diff_buffer, bytes_read)) - { - char begbuf[INT_BUFSIZE_BOUND (off_t)]; - report_difference (file->stat_info, - _("File fragment at %s is not a hole"), - offtostr (beg, begbuf)); + offset, rdsize); return false; } - beg += bytes_read; + if (bytes_read == 0 + || !zero_block_p (diff_buffer, bytes_read)) + { + char begbuf[INT_BUFSIZE_BOUND (off_t)]; + const char *msg = bytes_read ? _("File fragment at %s is not a hole") + : _("Hole starting at %s is truncated"); + + report_difference (file->stat_info, msg, offtostr (beg, begbuf)); + return false; + } + + offset += bytes_read; } return true; } @@ -542,7 +557,8 @@ check_data_region (struct tar_sparse_file *file, size_t i) file->dumped_size += bytes_read; size_left -= bytes_read; mv_size_left (file->stat_info->archive_file_size - file->dumped_size); - if (memcmp (blk->buffer, diff_buffer, rdsize)) + if (bytes_read == 0 + || memcmp (blk->buffer, diff_buffer, bytes_read)) { report_difference (file->stat_info, _("Contents differ")); return false; -- 1.8.5.3