From e9f5e7081c62a47fb875569403e37cacca26dc12 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Fri, 4 Oct 2019 12:31:32 -0700 Subject: [PATCH] Fix sparse file offset overflow on 32-bit systems On architectures where ssize_t is 32 bits but file offsets are 64 bits (such as 32-bit Linux with _FILE_OFFSET_BITS=64), the POSIX disk reader would incorrectly skip large sparse regions due to a 32-bit integer overflow in _archive_read_data_block(). This can result in the reader failing with "Encountered out-of-order sparse blocks", since the overflowed value is interpreted as a signed number and added to the current offset. The bytes variable was used to store the difference between two 64-bit integers, but bytes is a ssize_t. Since this value of bytes was not used after the block handling sparse offsets (it is always overwritten in the block below), replace it with an int64_t sparse_bytes variable that can always represent the difference without truncation. Signed-off-by: Daniel Verkamp --- libarchive/archive_read_disk_posix.c | 7 ++++--- libarchive/test/test_sparse_basic.c | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/libarchive/archive_read_disk_posix.c b/libarchive/archive_read_disk_posix.c index 87963c3c7..f62d182e8 100644 --- a/libarchive/archive_read_disk_posix.c +++ b/libarchive/archive_read_disk_posix.c @@ -694,6 +694,7 @@ _archive_read_data_block(struct archive *_a, const void **buff, struct tree *t = a->tree; int r; ssize_t bytes; + int64_t sparse_bytes; size_t buffbytes; int empty_sparse_region = 0; @@ -792,9 +793,9 @@ _archive_read_data_block(struct archive *_a, const void **buff, a->archive.state = ARCHIVE_STATE_FATAL; goto abort_read_data; } - bytes = t->current_sparse->offset - t->entry_total; - t->entry_remaining_bytes -= bytes; - t->entry_total += bytes; + sparse_bytes = t->current_sparse->offset - t->entry_total; + t->entry_remaining_bytes -= sparse_bytes; + t->entry_total += sparse_bytes; } /* diff --git a/libarchive/test/test_sparse_basic.c b/libarchive/test/test_sparse_basic.c index f12b6af48..5ad591be8 100644 --- a/libarchive/test/test_sparse_basic.c +++ b/libarchive/test/test_sparse_basic.c @@ -127,7 +127,7 @@ create_sparse_file(const char *path, const struct sparse *s) assert(DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dmy, NULL) != 0); - size_t offsetSoFar = 0; + uint64_t offsetSoFar = 0; while (s->type != END) { if (s->type == HOLE) { @@ -282,7 +282,7 @@ create_sparse_file(const char *path, const struct sparse *s) { char buff[1024]; int fd; - size_t total_size = 0; + uint64_t total_size = 0; const struct sparse *cur = s; memset(buff, ' ', sizeof(buff)); @@ -555,6 +555,12 @@ DEFINE_TEST(test_sparse_basic) { HOLE, 1 }, { DATA, 10240 }, { END, 0 } }; + const struct sparse sparse_file4[] = { + { DATA, 4096 }, { HOLE, 0xc0000000 }, + /* This hole overflows the offset if stored in 32 bits. */ + { DATA, 4096 }, { HOLE, 0x50000000 }, + { END, 0 } + }; /* * Test for the case that sparse data indicates just the whole file @@ -596,6 +602,7 @@ DEFINE_TEST(test_sparse_basic) verify_sparse_file(a, "file2", sparse_file2, 20); /* Encoded non sparse; expect a data block but no sparse entries. */ verify_sparse_file(a, "file3", sparse_file3, 0); + verify_sparse_file(a, "file4", sparse_file4, 2); assertEqualInt(ARCHIVE_OK, archive_read_free(a)); -- 2.47.2