9ae3a8
From f48ffc234c0aa2017dc90c971bbb50d47362192b Mon Sep 17 00:00:00 2001
9ae3a8
From: Max Reitz <mreitz@redhat.com>
9ae3a8
Date: Sat, 13 Jun 2015 16:22:04 +0200
9ae3a8
Subject: [PATCH 10/42] qcow2: Check L1/L2/reftable entries for alignment
9ae3a8
9ae3a8
Message-id: <1434212556-3927-11-git-send-email-mreitz@redhat.com>
9ae3a8
Patchwork-id: 66029
9ae3a8
O-Subject: [RHEL-7.2 qemu-kvm PATCH 10/42] qcow2: Check L1/L2/reftable entries for alignment
9ae3a8
Bugzilla: 1129893
9ae3a8
RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
9ae3a8
RH-Acked-by: Fam Zheng <famz@redhat.com>
9ae3a8
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
9ae3a8
9ae3a8
BZ: 1129893
9ae3a8
9ae3a8
Offsets taken from the L1, L2 and refcount tables are generally assumed
9ae3a8
to be correctly aligned. However, this cannot be guaranteed if the image
9ae3a8
has been written to by something different than qemu, thus check all
9ae3a8
offsets taken from these tables for correct cluster alignment.
9ae3a8
9ae3a8
Signed-off-by: Max Reitz <mreitz@redhat.com>
9ae3a8
Reviewed-by: Eric Blake <eblake@redhat.com>
9ae3a8
Message-id: 1409926039-29044-5-git-send-email-mreitz@redhat.com
9ae3a8
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
9ae3a8
(cherry picked from commit a97c67ee6c1546b985c1048c7a1f9e4fc13d9ee1)
9ae3a8
9ae3a8
Signed-off-by: Max Reitz <mreitz@redhat.com>
9ae3a8
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
9ae3a8
---
9ae3a8
 block/qcow2-cluster.c  | 43 ++++++++++++++++++++++++++++++++++++++++---
9ae3a8
 block/qcow2-refcount.c | 44 ++++++++++++++++++++++++++++++++++++++++++--
9ae3a8
 2 files changed, 82 insertions(+), 5 deletions(-)
9ae3a8
9ae3a8
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
9ae3a8
index f2f1170..0e3b8d7 100644
9ae3a8
--- a/block/qcow2-cluster.c
9ae3a8
+++ b/block/qcow2-cluster.c
9ae3a8
@@ -459,6 +459,13 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
9ae3a8
         goto out;
9ae3a8
     }
9ae3a8
 
9ae3a8
+    if (offset_into_cluster(s, l2_offset)) {
9ae3a8
+        qcow2_signal_corruption(bs, true, -1, -1, "L2 table offset %#" PRIx64
9ae3a8
+                                " unaligned (L1 index: %#" PRIx64 ")",
9ae3a8
+                                l2_offset, l1_index);
9ae3a8
+        return -EIO;
9ae3a8
+    }
9ae3a8
+
9ae3a8
     /* load the l2 table in memory */
9ae3a8
 
9ae3a8
     ret = l2_load(bs, l2_offset, &l2_table);
9ae3a8
@@ -481,8 +488,11 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
9ae3a8
         break;
9ae3a8
     case QCOW2_CLUSTER_ZERO:
9ae3a8
         if (s->qcow_version < 3) {
9ae3a8
-            qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
9ae3a8
-            return -EIO;
9ae3a8
+            qcow2_signal_corruption(bs, true, -1, -1, "Zero cluster entry found"
9ae3a8
+                                    " in pre-v3 image (L2 offset: %#" PRIx64
9ae3a8
+                                    ", L2 index: %#x)", l2_offset, l2_index);
9ae3a8
+            ret = -EIO;
9ae3a8
+            goto fail;
9ae3a8
         }
9ae3a8
         c = count_contiguous_clusters(nb_clusters, s->cluster_size,
9ae3a8
                 &l2_table[l2_index], 0,
9ae3a8
@@ -500,6 +510,14 @@ int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
9ae3a8
                 &l2_table[l2_index], 0,
9ae3a8
                 QCOW_OFLAG_COMPRESSED | QCOW_OFLAG_ZERO);
9ae3a8
         *cluster_offset &= L2E_OFFSET_MASK;
9ae3a8
+        if (offset_into_cluster(s, *cluster_offset)) {
9ae3a8
+            qcow2_signal_corruption(bs, true, -1, -1, "Data cluster offset %#"
9ae3a8
+                                    PRIx64 " unaligned (L2 offset: %#" PRIx64
9ae3a8
+                                    ", L2 index: %#x)", *cluster_offset,
9ae3a8
+                                    l2_offset, l2_index);
9ae3a8
+            ret = -EIO;
9ae3a8
+            goto fail;
9ae3a8
+        }
9ae3a8
         break;
9ae3a8
     default:
9ae3a8
         abort();
9ae3a8
@@ -516,6 +534,10 @@ out:
9ae3a8
     *num = nb_available - index_in_cluster;
9ae3a8
 
9ae3a8
     return ret;
9ae3a8
+
9ae3a8
+fail:
9ae3a8
+    qcow2_cache_put(bs, s->l2_table_cache, (void **)&l2_table);
9ae3a8
+    return ret;
9ae3a8
 }
9ae3a8
 
9ae3a8
 /*
9ae3a8
@@ -551,6 +573,12 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
9ae3a8
 
9ae3a8
     assert(l1_index < s->l1_size);
9ae3a8
     l2_offset = s->l1_table[l1_index] & L1E_OFFSET_MASK;
9ae3a8
+    if (offset_into_cluster(s, l2_offset)) {
9ae3a8
+        qcow2_signal_corruption(bs, true, -1, -1, "L2 table offset %#" PRIx64
9ae3a8
+                                " unaligned (L1 index: %#" PRIx64 ")",
9ae3a8
+                                l2_offset, l1_index);
9ae3a8
+        return -EIO;
9ae3a8
+    }
9ae3a8
 
9ae3a8
     /* seek the l2 table of the given l2 offset */
9ae3a8
 
9ae3a8
@@ -918,6 +946,15 @@ static int handle_copied(BlockDriverState *bs, uint64_t guest_offset,
9ae3a8
         bool offset_matches =
9ae3a8
             (cluster_offset & L2E_OFFSET_MASK) == *host_offset;
9ae3a8
 
9ae3a8
+        if (offset_into_cluster(s, cluster_offset & L2E_OFFSET_MASK)) {
9ae3a8
+            qcow2_signal_corruption(bs, true, -1, -1, "Data cluster offset "
9ae3a8
+                                    "%#llx unaligned (guest offset: %#" PRIx64
9ae3a8
+                                    ")", cluster_offset & L2E_OFFSET_MASK,
9ae3a8
+                                    guest_offset);
9ae3a8
+            ret = -EIO;
9ae3a8
+            goto out;
9ae3a8
+        }
9ae3a8
+
9ae3a8
         if (*host_offset != 0 && !offset_matches) {
9ae3a8
             *bytes = 0;
9ae3a8
             ret = 0;
9ae3a8
@@ -949,7 +986,7 @@ out:
9ae3a8
 
9ae3a8
     /* Only return a host offset if we actually made progress. Otherwise we
9ae3a8
      * would make requirements for handle_alloc() that it can't fulfill */
9ae3a8
-    if (ret) {
9ae3a8
+    if (ret > 0) {
9ae3a8
         *host_offset = (cluster_offset & L2E_OFFSET_MASK)
9ae3a8
                      + offset_into_cluster(s, guest_offset);
9ae3a8
     }
9ae3a8
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
9ae3a8
index dbcc4c6..e66d593 100644
9ae3a8
--- a/block/qcow2-refcount.c
9ae3a8
+++ b/block/qcow2-refcount.c
9ae3a8
@@ -101,6 +101,13 @@ static int get_refcount(BlockDriverState *bs, int64_t cluster_index)
9ae3a8
     if (!refcount_block_offset)
9ae3a8
         return 0;
9ae3a8
 
9ae3a8
+    if (offset_into_cluster(s, refcount_block_offset)) {
9ae3a8
+        qcow2_signal_corruption(bs, true, -1, -1, "Refblock offset %#" PRIx64
9ae3a8
+                                " unaligned (reftable index: %#" PRIx64 ")",
9ae3a8
+                                refcount_block_offset, refcount_table_index);
9ae3a8
+        return -EIO;
9ae3a8
+    }
9ae3a8
+
9ae3a8
     ret = qcow2_cache_get(bs, s->refcount_block_cache, refcount_block_offset,
9ae3a8
         (void**) &refcount_block);
9ae3a8
     if (ret < 0) {
9ae3a8
@@ -174,6 +181,14 @@ static int alloc_refcount_block(BlockDriverState *bs,
9ae3a8
 
9ae3a8
         /* If it's already there, we're done */
9ae3a8
         if (refcount_block_offset) {
9ae3a8
+            if (offset_into_cluster(s, refcount_block_offset)) {
9ae3a8
+                qcow2_signal_corruption(bs, true, -1, -1, "Refblock offset %#"
9ae3a8
+                                        PRIx64 " unaligned (reftable index: "
9ae3a8
+                                        "%#x)", refcount_block_offset,
9ae3a8
+                                        refcount_table_index);
9ae3a8
+                return -EIO;
9ae3a8
+            }
9ae3a8
+
9ae3a8
              return load_refcount_block(bs, refcount_block_offset,
9ae3a8
                  (void**) refcount_block);
9ae3a8
         }
9ae3a8
@@ -812,8 +827,14 @@ void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
9ae3a8
     case QCOW2_CLUSTER_NORMAL:
9ae3a8
     case QCOW2_CLUSTER_ZERO:
9ae3a8
         if (l2_entry & L2E_OFFSET_MASK) {
9ae3a8
-            qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
9ae3a8
-                                nb_clusters << s->cluster_bits, type);
9ae3a8
+            if (offset_into_cluster(s, l2_entry & L2E_OFFSET_MASK)) {
9ae3a8
+                qcow2_signal_corruption(bs, false, -1, -1,
9ae3a8
+                                        "Cannot free unaligned cluster %#llx",
9ae3a8
+                                        l2_entry & L2E_OFFSET_MASK);
9ae3a8
+            } else {
9ae3a8
+                qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
9ae3a8
+                                    nb_clusters << s->cluster_bits, type);
9ae3a8
+            }
9ae3a8
         }
9ae3a8
         break;
9ae3a8
     case QCOW2_CLUSTER_UNALLOCATED:
9ae3a8
@@ -872,6 +893,14 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
9ae3a8
             old_l2_offset = l2_offset;
9ae3a8
             l2_offset &= L1E_OFFSET_MASK;
9ae3a8
 
9ae3a8
+            if (offset_into_cluster(s, l2_offset)) {
9ae3a8
+                qcow2_signal_corruption(bs, true, -1, -1, "L2 table offset %#"
9ae3a8
+                                        PRIx64 " unaligned (L1 index: %#x)",
9ae3a8
+                                        l2_offset, i);
9ae3a8
+                ret = -EIO;
9ae3a8
+                goto fail;
9ae3a8
+            }
9ae3a8
+
9ae3a8
             ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset,
9ae3a8
                 (void**) &l2_table);
9ae3a8
             if (ret < 0) {
9ae3a8
@@ -905,6 +934,17 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
9ae3a8
 
9ae3a8
                     case QCOW2_CLUSTER_NORMAL:
9ae3a8
                     case QCOW2_CLUSTER_ZERO:
9ae3a8
+                        if (offset_into_cluster(s, offset & L2E_OFFSET_MASK)) {
9ae3a8
+                            qcow2_signal_corruption(bs, true, -1, -1, "Data "
9ae3a8
+                                                    "cluster offset %#llx "
9ae3a8
+                                                    "unaligned (L2 offset: %#"
9ae3a8
+                                                    PRIx64 ", L2 index: %#x)",
9ae3a8
+                                                    offset & L2E_OFFSET_MASK,
9ae3a8
+                                                    l2_offset, j);
9ae3a8
+                            ret = -EIO;
9ae3a8
+                            goto fail;
9ae3a8
+                        }
9ae3a8
+
9ae3a8
                         cluster_index = (offset & L2E_OFFSET_MASK) >> s->cluster_bits;
9ae3a8
                         if (!cluster_index) {
9ae3a8
                             /* unallocated */
9ae3a8
-- 
9ae3a8
1.8.3.1
9ae3a8