9ae3a8
From 6707cb9501355de8244d224b3a8140c0fe62a983 Mon Sep 17 00:00:00 2001
9ae3a8
From: Kevin Wolf <kwolf@redhat.com>
9ae3a8
Date: Tue, 25 Mar 2014 14:23:30 +0100
9ae3a8
Subject: [PATCH 23/49] qcow2: Validate refcount table offset
9ae3a8
9ae3a8
RH-Author: Kevin Wolf <kwolf@redhat.com>
9ae3a8
Message-id: <1395753835-7591-24-git-send-email-kwolf@redhat.com>
9ae3a8
Patchwork-id: n/a
9ae3a8
O-Subject: [virt-devel] [EMBARGOED RHEL-7.0 qemu-kvm PATCH 23/48] qcow2: Validate refcount table offset
9ae3a8
Bugzilla: 1066691
9ae3a8
RH-Acked-by: Jeff Cody <jcody@redhat.com>
9ae3a8
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
9ae3a8
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
9ae3a8
9ae3a8
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1066691
9ae3a8
Upstream status: Series embargoed
9ae3a8
9ae3a8
The end of the refcount table must not exceed INT64_MAX so that integer
9ae3a8
overflows are avoided.
9ae3a8
9ae3a8
Also check for misaligned refcount table. Such images are invalid and
9ae3a8
probably the result of data corruption. Error out to avoid further
9ae3a8
corruption.
9ae3a8
9ae3a8
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
9ae3a8
---
9ae3a8
 block/qcow2.c              |   33 +++++++++++++++++++++++++++++++++
9ae3a8
 tests/qemu-iotests/080     |   13 +++++++++++++
9ae3a8
 tests/qemu-iotests/080.out |   10 ++++++++++
9ae3a8
 3 files changed, 56 insertions(+), 0 deletions(-)
9ae3a8
9ae3a8
diff --git a/block/qcow2.c b/block/qcow2.c
9ae3a8
index a7780ac..5513fac 100644
9ae3a8
--- a/block/qcow2.c
9ae3a8
+++ b/block/qcow2.c
9ae3a8
@@ -329,6 +329,32 @@ static int qcow2_check(BlockDriverState *bs, BdrvCheckResult *result,
9ae3a8
     return ret;
9ae3a8
 }
9ae3a8
 
9ae3a8
+static int validate_table_offset(BlockDriverState *bs, uint64_t offset,
9ae3a8
+                                 uint64_t entries, size_t entry_len)
9ae3a8
+{
9ae3a8
+    BDRVQcowState *s = bs->opaque;
9ae3a8
+    uint64_t size;
9ae3a8
+
9ae3a8
+    /* Use signed INT64_MAX as the maximum even for uint64_t header fields,
9ae3a8
+     * because values will be passed to qemu functions taking int64_t. */
9ae3a8
+    if (entries > INT64_MAX / entry_len) {
9ae3a8
+        return -EINVAL;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    size = entries * entry_len;
9ae3a8
+
9ae3a8
+    if (INT64_MAX - size < offset) {
9ae3a8
+        return -EINVAL;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    /* Tables must be cluster aligned */
9ae3a8
+    if (offset & (s->cluster_size - 1)) {
9ae3a8
+        return -EINVAL;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    return 0;
9ae3a8
+}
9ae3a8
+
9ae3a8
 static QemuOptsList qcow2_runtime_opts = {
9ae3a8
     .name = "qcow2",
9ae3a8
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_runtime_opts.head),
9ae3a8
@@ -589,6 +615,13 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
9ae3a8
         goto fail;
9ae3a8
     }
9ae3a8
 
9ae3a8
+    ret = validate_table_offset(bs, s->refcount_table_offset,
9ae3a8
+                                s->refcount_table_size, sizeof(uint64_t));
9ae3a8
+    if (ret < 0) {
9ae3a8
+        error_setg(errp, "Invalid reference count table offset");
9ae3a8
+        goto fail;
9ae3a8
+    }
9ae3a8
+
9ae3a8
     s->snapshots_offset = header.snapshots_offset;
9ae3a8
     s->nb_snapshots = header.nb_snapshots;
9ae3a8
 
9ae3a8
diff --git a/tests/qemu-iotests/080 b/tests/qemu-iotests/080
9ae3a8
index 6179e05..f58ac73 100755
9ae3a8
--- a/tests/qemu-iotests/080
9ae3a8
+++ b/tests/qemu-iotests/080
9ae3a8
@@ -45,6 +45,7 @@ _supported_os Linux
9ae3a8
 header_size=104
9ae3a8
 
9ae3a8
 offset_backing_file_offset=8
9ae3a8
+offset_refcount_table_offset=48
9ae3a8
 offset_refcount_table_clusters=56
9ae3a8
 offset_header_size=100
9ae3a8
 offset_ext_magic=$header_size
9ae3a8
@@ -76,6 +77,18 @@ poke_file "$TEST_IMG" "$offset_refcount_table_clusters" "\xff\xff\xff\xff"
9ae3a8
 poke_file "$TEST_IMG" "$offset_refcount_table_clusters" "\x00\x02\x00\x01"
9ae3a8
 { $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
9ae3a8
 
9ae3a8
+echo
9ae3a8
+echo "== Misaligned refcount table =="
9ae3a8
+_make_test_img 64M
9ae3a8
+poke_file "$TEST_IMG" "$offset_refcount_table_offset" "\x12\x34\x56\x78\x90\xab\xcd\xef"
9ae3a8
+{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
9ae3a8
+
9ae3a8
+echo
9ae3a8
+echo "== Huge refcount offset =="
9ae3a8
+_make_test_img 64M
9ae3a8
+poke_file "$TEST_IMG" "$offset_refcount_table_offset" "\xff\xff\xff\xff\xff\xff\x00\x00"
9ae3a8
+poke_file "$TEST_IMG" "$offset_refcount_table_clusters" "\x00\x00\x00\x7f"
9ae3a8
+{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir
9ae3a8
 
9ae3a8
 # success, all done
9ae3a8
 echo "*** done"
9ae3a8
diff --git a/tests/qemu-iotests/080.out b/tests/qemu-iotests/080.out
9ae3a8
index 6fef6d9..f919b58 100644
9ae3a8
--- a/tests/qemu-iotests/080.out
9ae3a8
+++ b/tests/qemu-iotests/080.out
9ae3a8
@@ -20,4 +20,14 @@ qemu-io: can't open device TEST_DIR/t.qcow2: Reference count table too large
9ae3a8
 no file open, try 'help open'
9ae3a8
 qemu-io: can't open device TEST_DIR/t.qcow2: Reference count table too large
9ae3a8
 no file open, try 'help open'
9ae3a8
+
9ae3a8
+== Misaligned refcount table ==
9ae3a8
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 
9ae3a8
+qemu-io: can't open device TEST_DIR/t.qcow2: Invalid reference count table offset
9ae3a8
+no file open, try 'help open'
9ae3a8
+
9ae3a8
+== Huge refcount offset ==
9ae3a8
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 
9ae3a8
+qemu-io: can't open device TEST_DIR/t.qcow2: Invalid reference count table offset
9ae3a8
+no file open, try 'help open'
9ae3a8
 *** done
9ae3a8
-- 
9ae3a8
1.7.1
9ae3a8