Re: [Qemu-devel] Re: qemu unchecked block read/write vulnerability

2008-02-26 Thread Daniel P. Berrange
On Tue, Feb 19, 2008 at 04:39:07PM +, Ian Jackson wrote:
Content-Description: message body text
 I was doing some merging of qemu and I noticed that the block driver
 backends don't check the guest's read/write attempts against the
 nominal size of the block device.
 
 I haven't checked all of the backends but I have verified the bug with
 block-cow.c, which I have in my test induced to set a bitmap bit at an
 address which is not actually part of the bitmap.  In my tests I used
 as my guest a Linux kernel which I'd specially modifed to allow me to
 access out-of-range blocks.
 
 I think the fix is probably to insert a couple of range checks in the
 generic block dispatch layer and I attach a patch to achieve this.

FYI, this patch appears to cause massive unrecoverable data corruption for
qcow2 format disks. It looks like the sector range check is being applied
to the total sector count of the actual qcow datafile on disk, rather
than the total sector count of the logical disk. I suspect the same may
occur with other non-raw disk formats, so be wary

Dan.
-- 
|=- Red Hat, Engineering, Emerging Technologies, Boston.  +1 978 392 2496 -=|
|=-   Perl modules: http://search.cpan.org/~danberr/  -=|
|=-   Projects: http://freshmeat.net/~danielpb/   -=|
|=-  GnuPG: 7D3B9505   F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505  -=| 




Re: [Qemu-devel] Re: qemu unchecked block read/write vulnerability

2008-02-26 Thread Daniel P. Berrange
On Tue, Feb 26, 2008 at 07:46:51PM +, Daniel P. Berrange wrote:
 On Tue, Feb 19, 2008 at 04:39:07PM +, Ian Jackson wrote:
 Content-Description: message body text
  I was doing some merging of qemu and I noticed that the block driver
  backends don't check the guest's read/write attempts against the
  nominal size of the block device.
  
  I haven't checked all of the backends but I have verified the bug with
  block-cow.c, which I have in my test induced to set a bitmap bit at an
  address which is not actually part of the bitmap.  In my tests I used
  as my guest a Linux kernel which I'd specially modifed to allow me to
  access out-of-range blocks.
  
  I think the fix is probably to insert a couple of range checks in the
  generic block dispatch layer and I attach a patch to achieve this.
 
 FYI, this patch appears to cause massive unrecoverable data corruption for
 qcow2 format disks. It looks like the sector range check is being applied
 to the total sector count of the actual qcow datafile on disk, rather
 than the total sector count of the logical disk. I suspect the same may
 occur with other non-raw disk formats, so be wary

The original patch adds checks to the main  bdrv_XXX apis to validate that
the I/O operation does not exceed the bounds of the disk - ie beyond the
total_sectors count. This works correctly for bdrv_XXX calls from the IDE
driver.  With disk formats like QCow though,  bdrv_XXX is re-entrant, 
because the QCow driver uses the block APIs for dealing with its underlying
file.  The problem is that QCow files are grow-on-demand, so writes will
*explicitly* be beyond the end of the file. The original patch blocks any
I/O operation which would cause the QCow file to grow, resulting it more
or less catasatrophic data loss.  

Basically the bounds checking needs to distinguish between checking for
the logical disk extents, vs the physical disk extents. For raw files
these are the same so initial tests showed no problems, but for QCow
format disks they are different  thus we see a problem

What follows is a revised patch which introduces a flag BDRV_O_AUTOGROW
which can be passed to bdrv_open to indicate that the files can be allowed
to automatically extend their extents. This flag should only be used by
internal block drivers such as block-qcow2.c, block-vmdk.c  In my testing
this has fixed the qcow corruption, and still maintains the goal of Ian's
original patch which was to prevent the guest VM writing beyond the logical
disk extents.

 block-qcow.c  |2 -
 block-qcow2.c |2 -
 block-vmdk.c  |2 -
 block.c   |   74 ++
 block.h   |1 
 block_int.h   |1 
 6 files changed, 79 insertions(+), 3 deletions(-)

Dan.

Index: block-qcow.c
===
RCS file: /sources/qemu/qemu/block-qcow.c,v
retrieving revision 1.15
diff -u -p -r1.15 block-qcow.c
--- block-qcow.c11 Nov 2007 02:51:16 -  1.15
+++ block-qcow.c26 Feb 2008 23:46:41 -
@@ -95,7 +95,7 @@ static int qcow_open(BlockDriverState *b
 int len, i, shift, ret;
 QCowHeader header;
 
-ret = bdrv_file_open(s-hd, filename, flags);
+ret = bdrv_file_open(s-hd, filename, flags | BDRV_O_AUTOGROW);
 if (ret  0)
 return ret;
 if (bdrv_pread(s-hd, 0, header, sizeof(header)) != sizeof(header))
Index: block-qcow2.c
===
RCS file: /sources/qemu/qemu/block-qcow2.c,v
retrieving revision 1.10
diff -u -p -r1.10 block-qcow2.c
--- block-qcow2.c   11 Nov 2007 02:51:16 -  1.10
+++ block-qcow2.c   26 Feb 2008 23:46:41 -
@@ -191,7 +191,7 @@ static int qcow_open(BlockDriverState *b
 int len, i, shift, ret;
 QCowHeader header;
 
-ret = bdrv_file_open(s-hd, filename, flags);
+ret = bdrv_file_open(s-hd, filename, flags | BDRV_O_AUTOGROW);
 if (ret  0)
 return ret;
 if (bdrv_pread(s-hd, 0, header, sizeof(header)) != sizeof(header))
Index: block-vmdk.c
===
RCS file: /sources/qemu/qemu/block-vmdk.c,v
retrieving revision 1.19
diff -u -p -r1.19 block-vmdk.c
--- block-vmdk.c14 Jan 2008 03:48:37 -  1.19
+++ block-vmdk.c26 Feb 2008 23:46:41 -
@@ -378,7 +378,7 @@ static int vmdk_open(BlockDriverState *b
 flags = BDRV_O_RDONLY;
 fprintf(stderr, (VMDK) image open: flags=0x%x filename=%s\n, flags, 
bs-filename);
 
-ret = bdrv_file_open(s-hd, filename, flags);
+ret = bdrv_file_open(s-hd, filename, flags | BDRV_O_AUTOGROW);
 if (ret  0)
 return ret;
 if (bdrv_pread(s-hd, 0, magic, sizeof(magic)) != sizeof(magic))
Index: block.c
===
RCS file: /sources/qemu/qemu/block.c,v
retrieving revision 1.53
diff -u -p -r1.53 block.c
--- block.c 24 Dec 2007 16:10:43 -  1.53
+++ 

[Qemu-devel] Re: qemu unchecked block read/write vulnerability

2008-02-19 Thread Ian Jackson
I was doing some merging of qemu and I noticed that the block driver
backends don't check the guest's read/write attempts against the
nominal size of the block device.

I haven't checked all of the backends but I have verified the bug with
block-cow.c, which I have in my test induced to set a bitmap bit at an
address which is not actually part of the bitmap.  In my tests I used
as my guest a Linux kernel which I'd specially modifed to allow me to
access out-of-range blocks.

I think the fix is probably to insert a couple of range checks in the
generic block dispatch layer and I attach a patch to achieve this.


andrzej zaborowski told me:
 Qemu never claimed to be secure. Of course it's better to be secure
 than not if it doesn't add a bad overhead.
...
 I'm no sure where the size check should be, doing it in the IDE driver
 would probably make more sense as some other users of bdrv_ functions
 already have such checks.  I guess also some block-* backends may
 already have such checks.  And they only make a difference for writes
 because reads will return errors.

It seems to me that the right place to make this check is in the
generic block layer, so that it applies to all block requests
regardless of source or driver.  That will avoid making this mistake
in future.

Ian.

diff --git a/block.c b/block.c
index 0f8ad7b..d7f1114 100644
--- a/block.c
+++ b/block.c
@@ -123,6 +123,24 @@ void path_combine(char *dest, int dest_size,
 }
 }
 
+static int bdrv_rw_badreq_sectors(BlockDriverState *bs,
+   int64_t sector_num, int nb_sectors)
+{
+return
+   nb_sectors  0 ||
+   nb_sectors  bs-total_sectors ||
+   sector_num  bs-total_sectors - nb_sectors;
+}
+
+static int bdrv_rw_badreq_bytes(BlockDriverState *bs,
+ int64_t offset, int count)
+{
+int64_t size = bs-total_sectors  SECTOR_BITS;
+return
+   count  0 ||
+   count  size ||
+   offset  size - count;
+}
 
 static void bdrv_register(BlockDriver *bdrv)
 {
@@ -375,6 +393,7 @@ int bdrv_open2(BlockDriverState *bs, const char *filename, 
int flags,
 }
 bs-drv = drv;
 bs-opaque = qemu_mallocz(drv-instance_size);
+bs-total_sectors = 0; /* driver will set if it does not do getlength */
 if (bs-opaque == NULL  drv-instance_size  0)
 return -1;
 /* Note: for compatibility, we open disk image files as RDWR, and
@@ -440,6 +459,7 @@ void bdrv_close(BlockDriverState *bs)
 bs-drv = NULL;
 
 /* call the change callback */
+   bs-total_sectors = 0;
 bs-media_changed = 1;
 if (bs-change_cb)
 bs-change_cb(bs-change_opaque);
@@ -505,6 +525,8 @@ int bdrv_read(BlockDriverState *bs, int64_t sector_num,
 if (!drv)
 return -ENOMEDIUM;
 
+if (bdrv_rw_badreq_sectors(bs, sector_num, nb_sectors))
+   return -EDOM;
 if (sector_num == 0  bs-boot_sector_enabled  nb_sectors  0) {
 memcpy(buf, bs-boot_sector_data, 512);
 sector_num++;
@@ -545,6 +567,8 @@ int bdrv_write(BlockDriverState *bs, int64_t sector_num,
 return -ENOMEDIUM;
 if (bs-read_only)
 return -EACCES;
+if (bdrv_rw_badreq_sectors(bs, sector_num, nb_sectors))
+   return -EDOM;
 if (sector_num == 0  bs-boot_sector_enabled  nb_sectors  0) {
 memcpy(bs-boot_sector_data, buf, 512);
 }
@@ -670,6 +694,8 @@ int bdrv_pread(BlockDriverState *bs, int64_t offset,
 return -ENOMEDIUM;
 if (!drv-bdrv_pread)
 return bdrv_pread_em(bs, offset, buf1, count1);
+if (bdrv_rw_badreq_bytes(bs, offset, count1))
+   return -EDOM;
 return drv-bdrv_pread(bs, offset, buf1, count1);
 }
 
@@ -685,6 +711,8 @@ int bdrv_pwrite(BlockDriverState *bs, int64_t offset,
 return -ENOMEDIUM;
 if (!drv-bdrv_pwrite)
 return bdrv_pwrite_em(bs, offset, buf1, count1);
+if (bdrv_rw_badreq_bytes(bs, offset, count1))
+   return -EDOM;
 return drv-bdrv_pwrite(bs, offset, buf1, count1);
 }
 
@@ -951,6 +979,8 @@ int bdrv_write_compressed(BlockDriverState *bs, int64_t 
sector_num,
 return -ENOMEDIUM;
 if (!drv-bdrv_write_compressed)
 return -ENOTSUP;
+if (bdrv_rw_badreq_sectors(bs, sector_num, nb_sectors))
+   return -EDOM;
 return drv-bdrv_write_compressed(bs, sector_num, buf, nb_sectors);
 }
 
@@ -1097,6 +1127,8 @@ BlockDriverAIOCB *bdrv_aio_read(BlockDriverState *bs, 
int64_t sector_num,
 
 if (!drv)
 return NULL;
+if (bdrv_rw_badreq_sectors(bs, sector_num, nb_sectors))
+   return NULL;
 
 /* XXX: we assume that nb_sectors == 0 is suppored by the async read */
 if (sector_num == 0  bs-boot_sector_enabled  nb_sectors  0) {
@@ -1128,6 +1160,8 @@ BlockDriverAIOCB *bdrv_aio_write(BlockDriverState *bs, 
int64_t sector_num,
 return NULL;
 if (bs-read_only)
 return NULL;
+if (bdrv_rw_badreq_sectors(bs, sector_num, nb_sectors))
+   return NULL;
 if (sector_num == 0