From: Kevin Wolf <kwolf@redhat.com>
Date: Wed, 26 Mar 2014 13:05:45 +0100
Subject: qcow2: Validate snapshot table offset/size (CVE-2014-0144)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This avoid unbounded memory allocation and fixes a potential buffer
overflow on 32 bit hosts.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
(cherry picked from commit ce48f2f441ca98885267af6fd636a7cb804ee646)
[AF: BNC#870439; error_setg() -> report_unsupported(), dropped iotest]
Signed-off-by: Andreas Färber <afaerber@suse.de>
---
 block/qcow2-snapshot.c | 29 ++++-------------------------
 block/qcow2.c          | 15 +++++++++++++++
 block/qcow2.h          | 29 ++++++++++++++++++++++++++++-
 3 files changed, 47 insertions(+), 26 deletions(-)

diff --git a/block/qcow2-snapshot.c b/block/qcow2-snapshot.c
index f4719d9909c5ae1e35d148d24f73..082852329c0477e92a2e8d143f24 100644
--- a/block/qcow2-snapshot.c
+++ b/block/qcow2-snapshot.c
@@ -26,31 +26,6 @@
 #include "block/block_int.h"
 #include "block/qcow2.h"
 
-typedef struct QEMU_PACKED QCowSnapshotHeader {
-    /* header is 8 byte aligned */
-    uint64_t l1_table_offset;
-
-    uint32_t l1_size;
-    uint16_t id_str_size;
-    uint16_t name_size;
-
-    uint32_t date_sec;
-    uint32_t date_nsec;
-
-    uint64_t vm_clock_nsec;
-
-    uint32_t vm_state_size;
-    uint32_t extra_data_size; /* for extension */
-    /* extra data follows */
-    /* id_str follows */
-    /* name follows  */
-} QCowSnapshotHeader;
-
-typedef struct QEMU_PACKED QCowSnapshotExtraData {
-    uint64_t vm_state_size_large;
-    uint64_t disk_size;
-} QCowSnapshotExtraData;
-
 void qcow2_free_snapshots(BlockDriverState *bs)
 {
     BDRVQcowState *s = bs->opaque;
@@ -325,6 +300,10 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info)
     uint64_t *l1_table = NULL;
     int64_t l1_table_offset;
 
+    if (s->nb_snapshots >= QCOW_MAX_SNAPSHOTS) {
+        return -EFBIG;
+    }
+
     memset(sn, 0, sizeof(*sn));
 
     /* Generate an ID if it wasn't passed */
diff --git a/block/qcow2.c b/block/qcow2.c
index e64ac3ca9a8aba161fcce77d22d8..4830d162d217b36c0a35dc5a0de8 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -469,6 +469,21 @@ static int qcow2_open(BlockDriverState *bs, int flags)
         goto fail;
     }
 
+    /* Snapshot table offset/length */
+    if (header.nb_snapshots > QCOW_MAX_SNAPSHOTS) {
+        report_unsupported(bs, "Too many snapshots");
+        ret = -EINVAL;
+        goto fail;
+    }
+
+    ret = validate_table_offset(bs, header.snapshots_offset,
+                                header.nb_snapshots,
+                                sizeof(QCowSnapshotHeader));
+    if (ret < 0) {
+        report_unsupported(bs, "Invalid snapshot table offset");
+        goto fail;
+    }
+
     s->snapshots_offset = header.snapshots_offset;
     s->nb_snapshots = header.nb_snapshots;
 
diff --git a/block/qcow2.h b/block/qcow2.h
index 718b52bacad056093fafbc9275cf..ad595d4006ba4c03a5637e9bdbf4 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -38,6 +38,7 @@
 #define QCOW_CRYPT_AES  1
 
 #define QCOW_MAX_CRYPT_CLUSTERS 32
+#define QCOW_MAX_SNAPSHOTS 65536
 
 /* indicate that the refcount of the referenced cluster is exactly one. */
 #define QCOW_OFLAG_COPIED     (1LL << 63)
@@ -82,6 +83,32 @@ typedef struct QCowHeader {
     uint32_t header_length;
 } QCowHeader;
 
+typedef struct QEMU_PACKED QCowSnapshotHeader {
+    /* header is 8 byte aligned */
+    uint64_t l1_table_offset;
+
+    uint32_t l1_size;
+    uint16_t id_str_size;
+    uint16_t name_size;
+
+    uint32_t date_sec;
+    uint32_t date_nsec;
+
+    uint64_t vm_clock_nsec;
+
+    uint32_t vm_state_size;
+    uint32_t extra_data_size; /* for extension */
+    /* extra data follows */
+    /* id_str follows */
+    /* name follows  */
+} QCowSnapshotHeader;
+
+typedef struct QEMU_PACKED QCowSnapshotExtraData {
+    uint64_t vm_state_size_large;
+    uint64_t disk_size;
+} QCowSnapshotExtraData;
+
+
 typedef struct QCowSnapshot {
     uint64_t l1_table_offset;
     uint32_t l1_size;
@@ -168,7 +195,7 @@ typedef struct BDRVQcowState {
     AES_KEY aes_decrypt_key;
     uint64_t snapshots_offset;
     int snapshots_size;
-    int nb_snapshots;
+    unsigned int nb_snapshots;
     QCowSnapshot *snapshots;
 
     int flags;
