]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Fix sparse file offset overflow on 32-bit systems 1260/head
authorDaniel Verkamp <dverkamp@chromium.org>
Fri, 4 Oct 2019 19:31:32 +0000 (12:31 -0700)
committerDaniel Verkamp <dverkamp@chromium.org>
Fri, 4 Oct 2019 23:41:01 +0000 (16:41 -0700)
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 <dverkamp@chromium.org>
libarchive/archive_read_disk_posix.c
libarchive/test/test_sparse_basic.c

index 87963c3c7c28b0b27ce298699f6af2ca7ee96a42..f62d182e8db21892501e79e0dd638b6b9861fdd5 100644 (file)
@@ -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;
        }
 
        /*
index f12b6af4862af6c80bc98a47a795c72629692b8c..5ad591be830d71345b58cc5c3d28488635559873 100644 (file)
@@ -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));