>From 7b91c43bef0348c8cc18037bec5aef668b51fc90 Mon Sep 17 00:00:00 2001
From: Daniel P. Berrange <berrange@redhat.com>
Date: Mon, 14 Jun 2010 15:53:59 +0100
Subject: [PATCH 01/10] Extract the backing store format as well as name, if available

When QEMU opens a backing store for a QCow2 file, it will
normally auto-probe for the format of the backing store,
rather than assuming it has the same format as the referencing
file. There is a QCow2 extension that allows an explicit format
for the backing store to be embedded in the referencing file.
This closes the auto-probing security hole in QEMU.

This backing store format can be useful for libvirt users
of virStorageFileGetMetadata, so extract this data and report
it.

QEMU does not require disk image backing store files to be in
the same format the file linkee. It will auto-probe the disk
format for the backing store when opening it. If the backing
store was intended to be a raw file this could be a security
hole, because a guest may have written data into its disk that
then makes the backing store look like a qcow2 file. If it can
trick QEMU into thinking the raw file is a qcow2 file, it can
access arbitrary files on the host by adding further backing
store links.

To address this, callers of virStorageFileGetMeta need to be
told of the backing store format. If no format is declared,
they can make a decision whether to allow format probing or
not.
---
 src/util/storage_file.c |  192 ++++++++++++++++++++++++++++++++++++++++------
 src/util/storage_file.h |    2 +
 2 files changed, 169 insertions(+), 25 deletions(-)

Index: libvirt-0.7.6/src/util/storage_file.c
===================================================================
--- libvirt-0.7.6.orig/src/util/storage_file.c
+++ libvirt-0.7.6/src/util/storage_file.c
@@ -70,15 +70,17 @@ struct FileTypeInfo {
     int qcowCryptOffset;  /* Byte offset from start of file
                            * where to find encryption mode,
                            * -1 if encryption is not used */
-    int (*getBackingStore)(virConnectPtr conn, char **res,
+    int (*getBackingStore)(virConnectPtr conn, char **res, int *format,
                            const unsigned char *buf, size_t buf_size);
 };
 
-static int cowGetBackingStore(virConnectPtr, char **,
+static int cowGetBackingStore(virConnectPtr, char **, int *,
                               const unsigned char *, size_t);
-static int qcowXGetBackingStore(virConnectPtr, char **,
+static int qcow1GetBackingStore(virConnectPtr, char **, int *,
                                 const unsigned char *, size_t);
-static int vmdk4GetBackingStore(virConnectPtr, char **,
+static int qcow2GetBackingStore(virConnectPtr, char **, int *,
+                                const unsigned char *, size_t);
+static int vmdk4GetBackingStore(virConnectPtr, char **, int *,
                                 const unsigned char *, size_t);
 
 
@@ -115,11 +117,11 @@ static struct FileTypeInfo const fileTyp
     /* QCow */
     { VIR_STORAGE_FILE_QCOW, "QFI", NULL,
       LV_BIG_ENDIAN, 4, 1,
-      4+4+8+4+4, 8, 1, 4+4+8+4+4+8+1+1+2, qcowXGetBackingStore },
+      4+4+8+4+4, 8, 1, 4+4+8+4+4+8+1+1+2, qcow1GetBackingStore },
     /* QCow 2 */
     { VIR_STORAGE_FILE_QCOW2, "QFI", NULL,
       LV_BIG_ENDIAN, 4, 2,
-      4+4+8+4+4, 8, 1, 4+4+8+4+4+8, qcowXGetBackingStore },
+      4+4+8+4+4, 8, 1, 4+4+8+4+4+8, qcow2GetBackingStore },
     /* VMDK 3 */
     /* XXX Untested
     { VIR_STORAGE_FILE_VMDK, "COWD", NULL,
@@ -141,11 +143,14 @@ static struct FileTypeInfo const fileTyp
 static int
 cowGetBackingStore(virConnectPtr conn,
                    char **res,
+                   int *format,
                    const unsigned char *buf,
                    size_t buf_size)
 {
 #define COW_FILENAME_MAXLEN 1024
     *res = NULL;
+    *format = VIR_STORAGE_FILE_AUTO;
+
     if (buf_size < 4+4+ COW_FILENAME_MAXLEN)
         return BACKING_STORE_INVALID;
     if (buf[4+4] == '\0') /* cow_header_v2.backing_file[0] */
@@ -159,32 +164,102 @@ cowGetBackingStore(virConnectPtr conn,
     return BACKING_STORE_OK;
 }
 
+
+#define QCOWX_HDR_BACKING_FILE_OFFSET 4+4
+#define QCOWX_HDR_BACKING_FILE_SIZE 4+4+8
+
+#define QCOW2_HDR_TOTAL_SIZE 4+4+8+4+4+8+4+4+8+8+4+4+8
+#define QCOW2_HDR_EXTENSION_END 0
+#define QCOW2_HDR_EXTENSION_BACKING_FORMAT 0xE2792ACA
+
+static int
+qcow2GetBackingStoreFormat(virConnectPtr conn ATTRIBUTE_UNUSED,
+                           int *format,
+                           const unsigned char *buf,
+                           size_t buf_size,
+                           size_t extension_start,
+                           size_t extension_end)
+{
+    size_t offset = extension_start;
+
+    /*
+     * The extensions take format of
+     *
+     * int32: magic
+     * int32: length
+     * byte[length]: payload
+     *
+     * Unknown extensions can be ignored by skipping
+     * over "length" bytes in the data stream.
+     */
+    while (offset < (buf_size-8) &&
+           offset < (extension_end-8)) {
+        int magic = (buf[offset] << 24) +
+            (buf[offset+1] << 16) +
+            (buf[offset+2] << 8) +
+            (buf[offset+3]);
+        int len = (buf[offset+4] << 24) +
+            (buf[offset+5] << 16) +
+            (buf[offset+6] << 8) +
+            (buf[offset+7]);
+
+        offset += 8;
+
+        if ((offset + len) > buf_size)
+            break;
+
+        switch (magic) {
+        case QCOW2_HDR_EXTENSION_END:
+            goto done;
+
+        case QCOW2_HDR_EXTENSION_BACKING_FORMAT:
+            if (buf[offset+len] != '\0')
+                break;
+            *format = virStorageFileFormatTypeFromString(
+                ((const char *)buf)+offset);
+            break;
+        }
+
+        offset += len;
+    }
+
+done:
+
+    return 0;
+}
+
+
 static int
 qcowXGetBackingStore(virConnectPtr conn,
                      char **res,
+                     int *format,
                      const unsigned char *buf,
-                     size_t buf_size)
+                     size_t buf_size,
+                     bool isQCow2)
 {
     unsigned long long offset;
     unsigned long size;
 
     *res = NULL;
-    if (buf_size < 4+4+8+4)
+    if (format)
+        *format = VIR_STORAGE_FILE_AUTO;
+
+    if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4)
         return BACKING_STORE_INVALID;
-    offset = (((unsigned long long)buf[4+4] << 56)
-              | ((unsigned long long)buf[4+4+1] << 48)
-              | ((unsigned long long)buf[4+4+2] << 40)
-              | ((unsigned long long)buf[4+4+3] << 32)
-              | ((unsigned long long)buf[4+4+4] << 24)
-              | ((unsigned long long)buf[4+4+5] << 16)
-              | ((unsigned long long)buf[4+4+6] << 8)
-              | buf[4+4+7]); /* QCowHeader.backing_file_offset */
+    offset = (((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET] << 56)
+              | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+1] << 48)
+              | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+2] << 40)
+              | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+3] << 32)
+              | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+4] << 24)
+              | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+5] << 16)
+              | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+6] << 8)
+              | buf[QCOWX_HDR_BACKING_FILE_OFFSET+7]); /* QCowHeader.backing_file_offset */
     if (offset > buf_size)
         return BACKING_STORE_INVALID;
-    size = ((buf[4+4+8] << 24)
-            | (buf[4+4+8+1] << 16)
-            | (buf[4+4+8+2] << 8)
-            | buf[4+4+8+3]); /* QCowHeader.backing_file_size */
+    size = ((buf[QCOWX_HDR_BACKING_FILE_SIZE] << 24)
+            | (buf[QCOWX_HDR_BACKING_FILE_SIZE+1] << 16)
+            | (buf[QCOWX_HDR_BACKING_FILE_SIZE+2] << 8)
+            | buf[QCOWX_HDR_BACKING_FILE_SIZE+3]); /* QCowHeader.backing_file_size */
     if (size == 0)
         return BACKING_STORE_OK;
     if (offset + size > buf_size || offset + size < offset)
@@ -197,13 +272,66 @@ qcowXGetBackingStore(virConnectPtr conn,
     }
     memcpy(*res, buf + offset, size);
     (*res)[size] = '\0';
+
+    /*
+     * Traditionally QCow2 files had a layout of
+     *
+     * [header]
+     * [backingStoreName]
+     *
+     * Although the backingStoreName typically followed
+     * the header immediately, this was not required by
+     * the format. By specifying a higher byte offset for
+     * the backing file offset in the header, it was
+     * possible to leave space between the header and
+     * start of backingStore.
+     *
+     * This hack is now used to store extensions to the
+     * qcow2 format:
+     *
+     * [header]
+     * [extensions]
+     * [backingStoreName]
+     *
+     * Thus the file region to search for extensions is
+     * between the end of the header (QCOW2_HDR_TOTAL_SIZE)
+     * and the start of the backingStoreName (offset)
+     */
+    if (isQCow2)
+        qcow2GetBackingStoreFormat(conn, format, buf, buf_size, QCOW2_HDR_TOTAL_SIZE, offset);
+
     return BACKING_STORE_OK;
 }
 
 
 static int
+qcow1GetBackingStore(virConnectPtr conn,
+                     char **res,
+                     int *format,
+                     const unsigned char *buf,
+                     size_t buf_size)
+{
+    /* QCow1 doesn't have the extensions capability
+     * used to store backing format */
+    *format = VIR_STORAGE_FILE_AUTO;
+    return qcowXGetBackingStore(conn, res, NULL, buf, buf_size, false);
+}
+
+static int
+qcow2GetBackingStore(virConnectPtr conn,
+                     char **res,
+                     int *format,
+                     const unsigned char *buf,
+                     size_t buf_size)
+{
+    return qcowXGetBackingStore(conn, res, format, buf, buf_size, true);
+}
+
+
+static int
 vmdk4GetBackingStore(virConnectPtr conn,
                      char **res,
+                     int *format,
                      const unsigned char *buf,
                      size_t buf_size)
 {
@@ -213,6 +341,12 @@ vmdk4GetBackingStore(virConnectPtr conn,
     size_t len;
 
     *res = NULL;
+    /* XXX should we set VMDK instead ?  QEMU does auto
+     * probing, but this isn't in compliance with the
+     * VMDK spec/VMWare impl which presumes only VMDK
+     * backing format
+     */
+    *format = VIR_STORAGE_FILE_AUTO;
 
     if (buf_size <= 0x200)
         return BACKING_STORE_INVALID;
@@ -365,9 +499,12 @@ virStorageFileGetMetadataFromFD(virConne
         /* Validation passed, we know the file format now */
         meta->format = fileTypeInfo[i].type;
         if (fileTypeInfo[i].getBackingStore != NULL) {
-            char *base;
+            char *backing;
+            int backingFormat;
 
-            switch (fileTypeInfo[i].getBackingStore(conn, &base, head, len)) {
+            switch (fileTypeInfo[i].getBackingStore(conn, &backing,
+                                                    &backingFormat,
+                                                    head, len)) {
             case BACKING_STORE_OK:
                 break;
 
@@ -377,13 +514,16 @@ virStorageFileGetMetadataFromFD(virConne
             case BACKING_STORE_ERROR:
                 return -1;
             }
-            if (base != NULL) {
-                meta->backingStore = absolutePathFromBaseFile(path, base);
-                VIR_FREE(base);
+            if (backing != NULL) {
+                meta->backingStore = absolutePathFromBaseFile(path, backing);
+                VIR_FREE(backing);
                 if (meta->backingStore == NULL) {
                     virReportOOMError(conn);
                     return -1;
                 }
+                meta->backingStoreFormat = backingFormat;
+            } else {
+                meta->backingStoreFormat = VIR_STORAGE_FILE_AUTO;
             }
         }
         return 0;
Index: libvirt-0.7.6/src/util/storage_file.h
===================================================================
--- libvirt-0.7.6.orig/src/util/storage_file.h
+++ libvirt-0.7.6/src/util/storage_file.h
@@ -28,6 +28,7 @@
 #include <stdbool.h>
 
 enum virStorageFileFormat {
+    VIR_STORAGE_FILE_AUTO = -1,
     VIR_STORAGE_FILE_RAW = 0,
     VIR_STORAGE_FILE_DIR,
     VIR_STORAGE_FILE_BOCHS,
@@ -47,6 +48,7 @@ VIR_ENUM_DECL(virStorageFileFormat);
 typedef struct _virStorageFileMetadata {
     int format;
     char *backingStore;
+    int backingStoreFormat;
     unsigned long long capacity;
     bool encrypted;
 } virStorageFileMetadata;
