From 136cd19d0522c03b6dccc3e344886feab6faee43 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 22 Jan 2014 15:47:10 +0000 Subject: Describe flaws in qcow/qcow2 encryption in the docs The qemu-img.texi / qemu-doc.texi files currently describe the qcow2/qcow2 encryption thus "Encryption uses the AES format which is very secure (128 bit keys). Use a long password (16 characters) to get maximum protection." While AES is indeed a strong encryption system, the way that QCow/QCow2 use it results in a poor/weak encryption system. Due to the use of predictable IVs, based on the sector number extended to 128 bits, it is vulnerable to chosen plaintext attacks which can reveal the existence of encrypted data. The direct use of the user passphrase as the encryption key also leads to an inability to change the passphrase of an image. If passphrase is ever compromised the image data will all be vulnerable, since it cannot be re-encrypted. The admin has to clone the image files with a new passphrase and then use a program like shred to secure erase all the old files. Recommend against any use of QCow/QCow2 encryption, directing users to dm-crypt / LUKS which can meet modern cryptography best practices. [Changed "Qcow" to "qcow" for consistency. --Stefan] Signed-off-by: Daniel P. Berrange Reviewed-by: Markus Armbruster Reviewed-by: Eric Blake Signed-off-by: Stefan Hajnoczi --- qemu-doc.texi | 23 ++++++++++++++++++++--- qemu-img.texi | 23 ++++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/qemu-doc.texi b/qemu-doc.texi index ce61f30..ad31f2d 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -547,10 +547,27 @@ File name of a base image (see @option{create} subcommand) @item backing_fmt Image format of the base image @item encryption -If this option is set to @code{on}, the image is encrypted. +If this option is set to @code{on}, the image is encrypted with 128-bit AES-CBC. + +The use of encryption in qcow and qcow2 images is considered to be flawed by +modern cryptography standards, suffering from a number of design problems: + +@itemize @minus +@item The AES-CBC cipher is used with predictable initialization vectors based +on the sector number. This makes it vulnerable to chosen plaintext attacks +which can reveal the existence of encrypted data. +@item The user passphrase is directly used as the encryption key. A poorly +chosen or short passphrase will compromise the security of the encryption. +@item In the event of the passphrase being compromised there is no way to +change the passphrase to protect data in any qcow images. The files must +be cloned, using a different encryption passphrase in the new file. The +original file must then be securely erased using a program like shred, +though even this is ineffective with many modern storage technologies. +@end itemize -Encryption uses the AES format which is very secure (128 bit keys). Use -a long password (16 characters) to get maximum protection. +Use of qcow / qcow2 encryption is thus strongly discouraged. Users are +recommended to use an alternative encryption technology such as the +Linux dm-crypt / LUKS system. @item cluster_size Changes the qcow2 cluster size (must be between 512 and 2M). Smaller cluster diff --git a/qemu-img.texi b/qemu-img.texi index 526d56a..f84590e 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -409,10 +409,27 @@ File name of a base image (see @option{create} subcommand) @item backing_fmt Image format of the base image @item encryption -If this option is set to @code{on}, the image is encrypted. +If this option is set to @code{on}, the image is encrypted with 128-bit AES-CBC. -Encryption uses the AES format which is very secure (128 bit keys). Use -a long password (16 characters) to get maximum protection. +The use of encryption in qcow and qcow2 images is considered to be flawed by +modern cryptography standards, suffering from a number of design problems: + +@itemize @minus +@item The AES-CBC cipher is used with predictable initialization vectors based +on the sector number. This makes it vulnerable to chosen plaintext attacks +which can reveal the existence of encrypted data. +@item The user passphrase is directly used as the encryption key. A poorly +chosen or short passphrase will compromise the security of the encryption. +@item In the event of the passphrase being compromised there is no way to +change the passphrase to protect data in any qcow images. The files must +be cloned, using a different encryption passphrase in the new file. The +original file must then be securely erased using a program like shred, +though even this is ineffective with many modern storage technologies. +@end itemize + +Use of qcow / qcow2 encryption is thus strongly discouraged. Users are +recommended to use an alternative encryption technology such as the +Linux dm-crypt / LUKS system. @item cluster_size Changes the qcow2 cluster size (must be between 512 and 2M). Smaller cluster -- cgit v1.1 From 55aff7f133b0eb20b2c8a2a3e1307240aab8044c Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Mon, 27 Jan 2014 16:09:12 -0500 Subject: block: remove QED .bdrv_make_empty implementation The QED .bdrv_make_empty() implementation does nothing but return -ENOTSUP, which causes problems in bdrv_commit(). Since the function stub exists for QED, it is called, which then always returns an error. The proper way to not support an optional driver function stub is to just not implement it, so let's remove the stub. Signed-off-by: Jeff Cody Reviewed-by: Benoit Canet Signed-off-by: Stefan Hajnoczi --- block/qed.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/block/qed.c b/block/qed.c index 694e6e2..b9ca7ac 100644 --- a/block/qed.c +++ b/block/qed.c @@ -731,11 +731,6 @@ static int64_t coroutine_fn bdrv_qed_co_get_block_status(BlockDriverState *bs, return cb.status; } -static int bdrv_qed_make_empty(BlockDriverState *bs) -{ - return -ENOTSUP; -} - static BDRVQEDState *acb_to_s(QEDAIOCB *acb) { return acb->common.bs->opaque; @@ -1617,7 +1612,6 @@ static BlockDriver bdrv_qed = { .bdrv_create = bdrv_qed_create, .bdrv_has_zero_init = bdrv_has_zero_init_1, .bdrv_co_get_block_status = bdrv_qed_co_get_block_status, - .bdrv_make_empty = bdrv_qed_make_empty, .bdrv_aio_readv = bdrv_qed_aio_readv, .bdrv_aio_writev = bdrv_qed_aio_writev, .bdrv_co_write_zeroes = bdrv_qed_co_write_zeroes, -- cgit v1.1 From 14b4a8b9c654b625dea0f532fae5722781fd0a7d Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Mon, 27 Jan 2014 16:09:13 -0500 Subject: block: remove qcow2 .bdrv_make_empty implementation The QCOW2 .bdrv_make_empty implementation always returns 0 for success, but does not actually do anything. The proper way to not support an optional driver function stub is to just not implement it, so let's remove the stub. Signed-off-by: Jeff Cody Reviewed-by: Benoit Canet Signed-off-by: Stefan Hajnoczi --- block/qcow2.c | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/block/qcow2.c b/block/qcow2.c index 2da62b8..99a1ad1 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1689,26 +1689,6 @@ static int qcow2_create(const char *filename, QEMUOptionParameter *options, return ret; } -static int qcow2_make_empty(BlockDriverState *bs) -{ -#if 0 - /* XXX: not correct */ - BDRVQcowState *s = bs->opaque; - uint32_t l1_length = s->l1_size * sizeof(uint64_t); - int ret; - - memset(s->l1_table, 0, l1_length); - if (bdrv_pwrite(bs->file, s->l1_table_offset, s->l1_table, l1_length) < 0) - return -1; - ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length); - if (ret < 0) - return ret; - - l2_cache_reset(bs); -#endif - return 0; -} - static coroutine_fn int qcow2_co_write_zeroes(BlockDriverState *bs, int64_t sector_num, int nb_sectors, BdrvRequestFlags flags) { @@ -2252,7 +2232,6 @@ static BlockDriver bdrv_qcow2 = { .bdrv_has_zero_init = bdrv_has_zero_init_1, .bdrv_co_get_block_status = qcow2_co_get_block_status, .bdrv_set_key = qcow2_set_key, - .bdrv_make_empty = qcow2_make_empty, .bdrv_co_readv = qcow2_co_readv, .bdrv_co_writev = qcow2_co_writev, -- cgit v1.1 From f43aa8e18ad83508c2786403e7230a584d357c8e Mon Sep 17 00:00:00 2001 From: Peter Lieven Date: Wed, 29 Jan 2014 09:34:16 +0100 Subject: block/vmdk: add basic .bdrv_check support this adds a basic vmdk corruption check. it should detect severe table corruptions and file truncation. Signed-off-by: Peter Lieven Reviewed-by: Fam Zheng Signed-off-by: Stefan Hajnoczi --- block/vmdk.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/block/vmdk.c b/block/vmdk.c index 99ca60f..e809e2e 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -1942,6 +1942,53 @@ static ImageInfo *vmdk_get_extent_info(VmdkExtent *extent) return info; } +static int vmdk_check(BlockDriverState *bs, BdrvCheckResult *result, + BdrvCheckMode fix) +{ + BDRVVmdkState *s = bs->opaque; + VmdkExtent *extent = NULL; + int64_t sector_num = 0; + int64_t total_sectors = bdrv_getlength(bs) / BDRV_SECTOR_SIZE; + int ret; + uint64_t cluster_offset; + + if (fix) { + return -ENOTSUP; + } + + for (;;) { + if (sector_num >= total_sectors) { + return 0; + } + extent = find_extent(s, sector_num, extent); + if (!extent) { + fprintf(stderr, + "ERROR: could not find extent for sector %" PRId64 "\n", + sector_num); + break; + } + ret = get_cluster_offset(bs, extent, NULL, + sector_num << BDRV_SECTOR_BITS, + 0, &cluster_offset); + if (ret == VMDK_ERROR) { + fprintf(stderr, + "ERROR: could not get cluster_offset for sector %" + PRId64 "\n", sector_num); + break; + } + if (ret == VMDK_OK && cluster_offset >= bdrv_getlength(extent->file)) { + fprintf(stderr, + "ERROR: cluster offset for sector %" + PRId64 " points after EOF\n", sector_num); + break; + } + sector_num += extent->cluster_sectors; + } + + result->corruptions++; + return 0; +} + static ImageInfoSpecific *vmdk_get_specific_info(BlockDriverState *bs) { int i; @@ -2015,6 +2062,7 @@ static BlockDriver bdrv_vmdk = { .instance_size = sizeof(BDRVVmdkState), .bdrv_probe = vmdk_probe, .bdrv_open = vmdk_open, + .bdrv_check = vmdk_check, .bdrv_reopen_prepare = vmdk_reopen_prepare, .bdrv_read = vmdk_co_read, .bdrv_write = vmdk_co_write, -- cgit v1.1 From fb0a078f3a84b5a609d528500eea36b69ace9b20 Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Wed, 29 Jan 2014 18:47:23 +0800 Subject: qemu-iotests: Drop assert_no_active_commit in case 040 It is exactly assert_no_active_block_jobs in iotests.py Signed-off-by: Fam Zheng Signed-off-by: Stefan Hajnoczi --- tests/qemu-iotests/040 | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index 72eaad5..734b6a6 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -35,12 +35,8 @@ test_img = os.path.join(iotests.test_dir, 'test.img') class ImageCommitTestCase(iotests.QMPTestCase): '''Abstract base class for image commit test cases''' - def assert_no_active_commit(self): - result = self.vm.qmp('query-block-jobs') - self.assert_qmp(result, 'return', []) - def run_commit_test(self, top, base): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top=top, base=base) self.assert_qmp(result, 'return', {}) @@ -59,7 +55,7 @@ class ImageCommitTestCase(iotests.QMPTestCase): self.assert_qmp(event, 'data/len', self.image_len) self.vm.qmp('block-job-complete', device='drive0') - self.assert_no_active_commit() + self.assert_no_active_block_jobs() self.vm.shutdown() class TestSingleDrive(ImageCommitTestCase): @@ -91,19 +87,19 @@ class TestSingleDrive(ImageCommitTestCase): self.assert_qmp(result, 'error/class', 'DeviceNotFound') def test_top_same_base(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % backing_img) self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % backing_img) def test_top_invalid(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % backing_img) self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Top image file badfile not found') def test_base_invalid(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='%s' % mid_img, base='badfile') self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Base \'badfile\' not found') @@ -114,13 +110,13 @@ class TestSingleDrive(ImageCommitTestCase): self.assertEqual(-1, qemu_io('-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed")) def test_top_and_base_reversed(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='%s' % backing_img, base='%s' % mid_img) self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % mid_img) def test_top_omitted(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0') self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', "Parameter 'top' is missing") @@ -181,19 +177,19 @@ class TestRelativePaths(ImageCommitTestCase): self.assert_qmp(result, 'error/class', 'DeviceNotFound') def test_top_same_base(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='%s' % self.mid_img) self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % self.mid_img) def test_top_invalid(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='badfile', base='%s' % self.backing_img) self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Top image file badfile not found') def test_base_invalid(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.mid_img, base='badfile') self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Base \'badfile\' not found') @@ -204,7 +200,7 @@ class TestRelativePaths(ImageCommitTestCase): self.assertEqual(-1, qemu_io('-c', 'read -P 0xef 524288 524288', self.backing_img_abs).find("verification failed")) def test_top_and_base_reversed(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top='%s' % self.backing_img, base='%s' % self.mid_img) self.assert_qmp(result, 'error/class', 'GenericError') self.assert_qmp(result, 'error/desc', 'Base \'%s\' not found' % self.mid_img) @@ -229,7 +225,7 @@ class TestSetSpeed(ImageCommitTestCase): os.remove(backing_img) def test_set_speed(self): - self.assert_no_active_commit() + self.assert_no_active_block_jobs() self.vm.pause_drive('drive0') result = self.vm.qmp('block-commit', device='drive0', top=mid_img, speed=1024 * 1024) -- cgit v1.1 From f50159fa9b5a0ad82e30c123643ec39a1df81d9a Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 29 Jan 2014 18:05:08 +0100 Subject: block/vhdx: Error checking fixes Errors are inadvertently ignored in a few places. Has always been broken. Spotted by Coverity. Signed-off-by: Markus Armbruster Reviewed-by: Jeff Cody Signed-off-by: Stefan Hajnoczi --- block/vhdx-log.c | 4 ++-- block/vhdx.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/block/vhdx-log.c b/block/vhdx-log.c index 8c9ae0d..02755b8 100644 --- a/block/vhdx-log.c +++ b/block/vhdx-log.c @@ -965,8 +965,8 @@ static int vhdx_log_write(BlockDriverState *bs, BDRVVHDXState *s, cpu_to_le32s((uint32_t *)(buffer + 4)); /* now write to the log */ - vhdx_log_write_sectors(bs, &s->log, §ors_written, buffer, - desc_sectors + sectors); + ret = vhdx_log_write_sectors(bs, &s->log, §ors_written, buffer, + desc_sectors + sectors); if (ret < 0) { goto exit; } diff --git a/block/vhdx.c b/block/vhdx.c index 9ee0a61..55689cf 100644 --- a/block/vhdx.c +++ b/block/vhdx.c @@ -374,7 +374,7 @@ static int vhdx_update_header(BlockDriverState *bs, BDRVVHDXState *s, inactive_header->log_guid = *log_guid; } - vhdx_write_header(bs->file, inactive_header, header_offset, true); + ret = vhdx_write_header(bs->file, inactive_header, header_offset, true); if (ret < 0) { goto exit; } @@ -1810,13 +1810,13 @@ static int vhdx_create(const char *filename, QEMUOptionParameter *options, creator = g_utf8_to_utf16("QEMU v" QEMU_VERSION, -1, NULL, &creator_items, NULL); signature = cpu_to_le64(VHDX_FILE_SIGNATURE); - bdrv_pwrite(bs, VHDX_FILE_ID_OFFSET, &signature, sizeof(signature)); + ret = bdrv_pwrite(bs, VHDX_FILE_ID_OFFSET, &signature, sizeof(signature)); if (ret < 0) { goto delete_and_exit; } if (creator) { - bdrv_pwrite(bs, VHDX_FILE_ID_OFFSET + sizeof(signature), creator, - creator_items * sizeof(gunichar2)); + ret = bdrv_pwrite(bs, VHDX_FILE_ID_OFFSET + sizeof(signature), + creator, creator_items * sizeof(gunichar2)); if (ret < 0) { goto delete_and_exit; } -- cgit v1.1 From 170a60345ee84dff3114f759367badfb85680728 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Thu, 30 Jan 2014 16:34:12 +0100 Subject: dataplane: Comment fix Signed-off-by: Markus Armbruster Signed-off-by: Stefan Hajnoczi --- hw/block/dataplane/virtio-blk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/block/dataplane/virtio-blk.c b/hw/block/dataplane/virtio-blk.c index 456d437..2237edb 100644 --- a/hw/block/dataplane/virtio-blk.c +++ b/hw/block/dataplane/virtio-blk.c @@ -145,7 +145,7 @@ static void do_get_id_cmd(VirtIOBlockDataPlane *s, { char id[VIRTIO_BLK_ID_BYTES]; - /* Serial number not NUL-terminated when shorter than buffer */ + /* Serial number not NUL-terminated when longer than buffer */ strncpy(id, s->blk->serial ? s->blk->serial : "", sizeof(id)); iov_from_buf(iov, iov_cnt, 0, id, sizeof(id)); complete_request_early(s, elem, inhdr, VIRTIO_BLK_S_OK); -- cgit v1.1 From 1b7650ef2f63d53cf89af25a9f323323cf2423a7 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Wed, 29 Jan 2014 16:33:54 +0100 Subject: qemu-iotests: only run 071 on qcow2 The 071 test is designed for IMGFMT=qcow2 because it uses the l2_load blkdebug event. Its output filtering also assumes that IMGFMT is not raw since 071.out contains "format=raw" but IMGFMT=raw would filter the output to "format=IMGFMT". Perhaps the test case can be rewritten to be more generic, but for now let's document that it was only supposed to work with qcow2. Signed-off-by: Stefan Hajnoczi Reviewed-by: Benoit Canet --- tests/qemu-iotests/071 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/qemu-iotests/071 b/tests/qemu-iotests/071 index 2a22546..dbc07c6 100755 --- a/tests/qemu-iotests/071 +++ b/tests/qemu-iotests/071 @@ -38,7 +38,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.rc . ./common.filter -_supported_fmt generic +_supported_fmt qcow2 _supported_proto generic _supported_os Linux -- cgit v1.1