[libvirt] [PATCH 01/11] Extract the backing store format as well as name, if available
Daniel Veillard
veillard at redhat.com
Thu Jul 15 16:06:02 UTC 2010
On Mon, Jul 12, 2010 at 02:30:38PM +0100, Daniel P. Berrange wrote:
> 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 | 206 +++++++++++++++++++++++++++++++++++++++++------
> src/util/storage_file.h | 2 +
> 2 files changed, 183 insertions(+), 25 deletions(-)
>
> diff --git a/src/util/storage_file.c b/src/util/storage_file.c
> index 0adea40..80f743e 100644
> --- a/src/util/storage_file.c
> +++ b/src/util/storage_file.c
> @@ -78,12 +78,33 @@ struct FileTypeInfo {
> int qcowCryptOffset; /* Byte offset from start of file
> * where to find encryption mode,
> * -1 if encryption is not used */
> - int (*getBackingStore)(char **res, const unsigned char *buf, size_t buf_size);
> + int (*getBackingStore)(char **res, int *format,
> + const unsigned char *buf, size_t buf_size);
> };
>
> -static int cowGetBackingStore(char **, const unsigned char *, size_t);
> -static int qcowXGetBackingStore(char **, const unsigned char *, size_t);
> -static int vmdk4GetBackingStore(char **, const unsigned char *, size_t);
> +static int cowGetBackingStore(char **, int *,
> + const unsigned char *, size_t);
> +static int qcow1GetBackingStore(char **, int *,
> + const unsigned char *, size_t);
> +static int qcow2GetBackingStore(char **, int *,
> + const unsigned char *, size_t);
> +static int vmdk4GetBackingStore(char **, int *,
> + const unsigned char *, size_t);
> +
> +#define QCOWX_HDR_VERSION (4)
> +#define QCOWX_HDR_BACKING_FILE_OFFSET (QCOWX_HDR_VERSION+4)
> +#define QCOWX_HDR_BACKING_FILE_SIZE (QCOWX_HDR_BACKING_FILE_OFFSET+8)
> +#define QCOWX_HDR_IMAGE_SIZE (QCOWX_HDR_BACKING_FILE_SIZE+4+4)
> +
> +#define QCOW1_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8+1+1)
> +#define QCOW2_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8)
> +
> +#define QCOW1_HDR_TOTAL_SIZE (QCOW1_HDR_CRYPT+4+8)
> +#define QCOW2_HDR_TOTAL_SIZE (QCOW2_HDR_CRYPT+4+4+8+8+4+4+8)
> +
> +#define QCOW2_HDR_EXTENSION_END 0
> +#define QCOW2_HDR_EXTENSION_BACKING_FORMAT 0xE2792ACA
> +
>
>
> static struct FileTypeInfo const fileTypeInfo[] = {
> @@ -119,11 +140,11 @@ static struct FileTypeInfo const fileTypeInfo[] = {
> /* 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 },
> + QCOWX_HDR_IMAGE_SIZE, 8, 1, QCOW1_HDR_CRYPT, 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 },
> + QCOWX_HDR_IMAGE_SIZE, 8, 1, QCOW2_HDR_CRYPT, qcow2GetBackingStore },
> /* VMDK 3 */
> /* XXX Untested
> { VIR_STORAGE_FILE_VMDK, "COWD", NULL,
> @@ -142,11 +163,14 @@ static struct FileTypeInfo const fileTypeInfo[] = {
>
> static int
> cowGetBackingStore(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] */
> @@ -160,31 +184,98 @@ cowGetBackingStore(char **res,
> return BACKING_STORE_OK;
> }
>
> +
> +static int
> +qcow2GetBackingStoreFormat(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)) {
> + unsigned int magic =
> + (buf[offset] << 24) +
> + (buf[offset+1] << 16) +
> + (buf[offset+2] << 8) +
> + (buf[offset+3]);
> + unsigned int len =
> + (buf[offset+4] << 24) +
> + (buf[offset+5] << 16) +
> + (buf[offset+6] << 8) +
> + (buf[offset+7]);
> +
> + offset += 8;
> +
> + if ((offset + len) < offset)
> + break;
> +
> + 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(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,12 +288,63 @@ qcowXGetBackingStore(char **res,
> }
> 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(format, buf, buf_size, QCOW2_HDR_TOTAL_SIZE, offset);
> +
> return BACKING_STORE_OK;
> }
>
>
> static int
> +qcow1GetBackingStore(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(res, NULL, buf, buf_size, false);
> +}
> +
> +static int
> +qcow2GetBackingStore(char **res,
> + int *format,
> + const unsigned char *buf,
> + size_t buf_size)
> +{
> + return qcowXGetBackingStore(res, format, buf, buf_size, true);
> +}
> +
> +
> +static int
> vmdk4GetBackingStore(char **res,
> + int *format,
> const unsigned char *buf,
> size_t buf_size)
> {
> @@ -212,6 +354,14 @@ vmdk4GetBackingStore(char **res,
> size_t len;
>
> *res = NULL;
> + /*
> + * Technically this should have been VMDK, since
> + * VMDK spec / VMWare impl only support VMDK backed
> + * by VMDK. QEMU isn't following this though and
> + * does probing on VMDK backing files, hence we set
> + * AUTO
> + */
> + *format = VIR_STORAGE_FILE_AUTO;
>
> if (buf_size <= 0x200)
> return BACKING_STORE_INVALID;
> @@ -358,9 +508,12 @@ virStorageFileGetMetadataFromFD(const char *path,
> /* 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(&base, head, len)) {
> + switch (fileTypeInfo[i].getBackingStore(&backing,
> + &backingFormat,
> + head, len)) {
> case BACKING_STORE_OK:
> break;
>
> @@ -370,13 +523,16 @@ virStorageFileGetMetadataFromFD(const char *path,
> 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();
> return -1;
> }
> + meta->backingStoreFormat = backingFormat;
> + } else {
> + meta->backingStoreFormat = VIR_STORAGE_FILE_AUTO;
> }
> }
> return 0;
> diff --git a/src/util/storage_file.h b/src/util/storage_file.h
> index 58533ee..6328ba7 100644
> --- a/src/util/storage_file.h
> +++ b/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;
ACK, also includes some cleanup about the indexes in the qcow formats
Daniel
--
Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/
daniel at veillard.com | Rpmfind RPM search engine http://rpmfind.net/
http://veillard.com/ | virtualization library http://libvirt.org/
More information about the libvir-list
mailing list