From d40593dd9033d39a5e4cc32915c5eb28f3e858b6 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Mon, 7 Jul 2014 16:38:58 +0200 Subject: block/backup: Fix hang for unaligned image size When doing a block backup of an image with an unaligned size (with respect to the BACKUP_CLUSTER_SIZE), qemu would check the allocation status of sectors after the end of the image. bdrv_is_allocated() returns a result that is valid for 0 sectors in this case, so the backup job ran into an endless loop. Stop looping when seeing a result valid for 0 sectors, we're at EOF then. The test case looks somewhat unrelated at first sight because I originally tried to reproduce a different suspected bug that turned out to not exist. Still a good test case and it accidentally found this one. Signed-off-by: Kevin Wolf Reviewed-by: Eric Blake --- block/backup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'block') diff --git a/block/backup.c b/block/backup.c index 7978ae2..d0b0225 100644 --- a/block/backup.c +++ b/block/backup.c @@ -307,7 +307,7 @@ static void coroutine_fn backup_run(void *opaque) BACKUP_SECTORS_PER_CLUSTER - i, &n); i += n; - if (alloced == 1) { + if (alloced == 1 || n == 0) { break; } } -- cgit v1.1 From 44deba5a52576508f27edadf953e435141e2a76a Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Thu, 3 Jul 2014 14:43:32 +0200 Subject: qcow2: Make qiov match request size until backing file EOF If a qcow2 image has a shorter backing file and a read request to unallocated clusters goes across EOF of the backing file, the backing file sees a shortened request and the rest is filled with zeros. However, the original too long qiov was used with the shortened request. This patch makes the qiov size match the request size, avoiding a potential buffer overflow in raw-posix. Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz --- block/qcow2.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'block') diff --git a/block/qcow2.c b/block/qcow2.c index 67e55c9..b0faa69 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1020,11 +1020,20 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num, n1 = qcow2_backing_read1(bs->backing_hd, &hd_qiov, sector_num, cur_nr_sectors); if (n1 > 0) { + QEMUIOVector local_qiov; + + qemu_iovec_init(&local_qiov, hd_qiov.niov); + qemu_iovec_concat(&local_qiov, &hd_qiov, 0, + n1 * BDRV_SECTOR_SIZE); + BLKDBG_EVENT(bs->file, BLKDBG_READ_BACKING_AIO); qemu_co_mutex_unlock(&s->lock); ret = bdrv_co_readv(bs->backing_hd, sector_num, - n1, &hd_qiov); + n1, &local_qiov); qemu_co_mutex_lock(&s->lock); + + qemu_iovec_destroy(&local_qiov); + if (ret < 0) { goto fail; } -- cgit v1.1 From f06ee3d4aa547df8d7d2317b2b6db7a88c1f3744 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 4 Jul 2014 17:11:28 +0200 Subject: qed: Make qiov match request size until backing file EOF If a QED image has a shorter backing file and a read request to unallocated clusters goes across EOF of the backing file, the backing file sees a shortened request and the rest is filled with zeros. However, the original too long qiov was used with the shortened request. This patch makes the qiov size match the request size, avoiding a potential buffer overflow in raw-posix. Signed-off-by: Kevin Wolf Reviewed-by: Eric Blake --- block/qed.c | 38 ++++++++++++++++++++++++++++++-------- block/qed.h | 1 + 2 files changed, 31 insertions(+), 8 deletions(-) (limited to 'block') diff --git a/block/qed.c b/block/qed.c index b69374b..cd4872b 100644 --- a/block/qed.c +++ b/block/qed.c @@ -761,17 +761,19 @@ static BDRVQEDState *acb_to_s(QEDAIOCB *acb) /** * Read from the backing file or zero-fill if no backing file * - * @s: QED state - * @pos: Byte position in device - * @qiov: Destination I/O vector - * @cb: Completion function - * @opaque: User data for completion function + * @s: QED state + * @pos: Byte position in device + * @qiov: Destination I/O vector + * @backing_qiov: Possibly shortened copy of qiov, to be allocated here + * @cb: Completion function + * @opaque: User data for completion function * * This function reads qiov->size bytes starting at pos from the backing file. * If there is no backing file then zeroes are read. */ static void qed_read_backing_file(BDRVQEDState *s, uint64_t pos, QEMUIOVector *qiov, + QEMUIOVector **backing_qiov, BlockDriverCompletionFunc *cb, void *opaque) { uint64_t backing_length = 0; @@ -804,15 +806,21 @@ static void qed_read_backing_file(BDRVQEDState *s, uint64_t pos, /* If the read straddles the end of the backing file, shorten it */ size = MIN((uint64_t)backing_length - pos, qiov->size); + assert(*backing_qiov == NULL); + *backing_qiov = g_new(QEMUIOVector, 1); + qemu_iovec_init(*backing_qiov, qiov->niov); + qemu_iovec_concat(*backing_qiov, qiov, 0, size); + BLKDBG_EVENT(s->bs->file, BLKDBG_READ_BACKING_AIO); bdrv_aio_readv(s->bs->backing_hd, pos / BDRV_SECTOR_SIZE, - qiov, size / BDRV_SECTOR_SIZE, cb, opaque); + *backing_qiov, size / BDRV_SECTOR_SIZE, cb, opaque); } typedef struct { GenericCB gencb; BDRVQEDState *s; QEMUIOVector qiov; + QEMUIOVector *backing_qiov; struct iovec iov; uint64_t offset; } CopyFromBackingFileCB; @@ -829,6 +837,12 @@ static void qed_copy_from_backing_file_write(void *opaque, int ret) CopyFromBackingFileCB *copy_cb = opaque; BDRVQEDState *s = copy_cb->s; + if (copy_cb->backing_qiov) { + qemu_iovec_destroy(copy_cb->backing_qiov); + g_free(copy_cb->backing_qiov); + copy_cb->backing_qiov = NULL; + } + if (ret) { qed_copy_from_backing_file_cb(copy_cb, ret); return; @@ -866,11 +880,12 @@ static void qed_copy_from_backing_file(BDRVQEDState *s, uint64_t pos, copy_cb = gencb_alloc(sizeof(*copy_cb), cb, opaque); copy_cb->s = s; copy_cb->offset = offset; + copy_cb->backing_qiov = NULL; copy_cb->iov.iov_base = qemu_blockalign(s->bs, len); copy_cb->iov.iov_len = len; qemu_iovec_init_external(©_cb->qiov, ©_cb->iov, 1); - qed_read_backing_file(s, pos, ©_cb->qiov, + qed_read_backing_file(s, pos, ©_cb->qiov, ©_cb->backing_qiov, qed_copy_from_backing_file_write, copy_cb); } @@ -1313,7 +1328,7 @@ static void qed_aio_read_data(void *opaque, int ret, return; } else if (ret != QED_CLUSTER_FOUND) { qed_read_backing_file(s, acb->cur_pos, &acb->cur_qiov, - qed_aio_next_io, acb); + &acb->backing_qiov, qed_aio_next_io, acb); return; } @@ -1339,6 +1354,12 @@ static void qed_aio_next_io(void *opaque, int ret) trace_qed_aio_next_io(s, acb, ret, acb->cur_pos + acb->cur_qiov.size); + if (acb->backing_qiov) { + qemu_iovec_destroy(acb->backing_qiov); + g_free(acb->backing_qiov); + acb->backing_qiov = NULL; + } + /* Handle I/O error */ if (ret) { qed_aio_complete(acb, ret); @@ -1378,6 +1399,7 @@ static BlockDriverAIOCB *qed_aio_setup(BlockDriverState *bs, acb->qiov_offset = 0; acb->cur_pos = (uint64_t)sector_num * BDRV_SECTOR_SIZE; acb->end_pos = acb->cur_pos + nb_sectors * BDRV_SECTOR_SIZE; + acb->backing_qiov = NULL; acb->request.l2_table = NULL; qemu_iovec_init(&acb->cur_qiov, qiov->niov); diff --git a/block/qed.h b/block/qed.h index b024751..2b0e724 100644 --- a/block/qed.h +++ b/block/qed.h @@ -142,6 +142,7 @@ typedef struct QEDAIOCB { /* Current cluster scatter-gather list */ QEMUIOVector cur_qiov; + QEMUIOVector *backing_qiov; uint64_t cur_pos; /* position on block device, in bytes */ uint64_t cur_cluster; /* cluster offset in image file */ unsigned int cur_nclusters; /* number of clusters being accessed */ -- cgit v1.1 From 8eb029c26ecf61da6c2b45c3c23472e8c477ba34 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Tue, 1 Jul 2014 16:09:54 +0200 Subject: block: Assert qiov length matches request length At least raw-posix relies on this because it can allocate bounce buffers based on the request length, but access it using all of the qiov entries later. Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz --- block/raw-posix.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'block') diff --git a/block/raw-posix.c b/block/raw-posix.c index a857def..2bcc73d 100644 --- a/block/raw-posix.c +++ b/block/raw-posix.c @@ -790,6 +790,7 @@ static ssize_t handle_aiocb_rw(RawPosixAIOData *aiocb) memcpy(p, aiocb->aio_iov[i].iov_base, aiocb->aio_iov[i].iov_len); p += aiocb->aio_iov[i].iov_len; } + assert(p - buf == aiocb->aio_nbytes); } nbytes = handle_aiocb_rw_linear(aiocb, buf); @@ -804,9 +805,11 @@ static ssize_t handle_aiocb_rw(RawPosixAIOData *aiocb) copy = aiocb->aio_iov[i].iov_len; } memcpy(aiocb->aio_iov[i].iov_base, p, copy); + assert(count >= copy); p += copy; count -= copy; } + assert(count == 0); } qemu_vfree(buf); @@ -993,12 +996,14 @@ static int paio_submit_co(BlockDriverState *bs, int fd, acb->aio_type = type; acb->aio_fildes = fd; + acb->aio_nbytes = nb_sectors * BDRV_SECTOR_SIZE; + acb->aio_offset = sector_num * BDRV_SECTOR_SIZE; + if (qiov) { acb->aio_iov = qiov->iov; acb->aio_niov = qiov->niov; + assert(qiov->size == acb->aio_nbytes); } - acb->aio_nbytes = nb_sectors * 512; - acb->aio_offset = sector_num * 512; trace_paio_submit_co(sector_num, nb_sectors, type); pool = aio_get_thread_pool(bdrv_get_aio_context(bs)); @@ -1016,12 +1021,14 @@ static BlockDriverAIOCB *paio_submit(BlockDriverState *bs, int fd, acb->aio_type = type; acb->aio_fildes = fd; + acb->aio_nbytes = nb_sectors * BDRV_SECTOR_SIZE; + acb->aio_offset = sector_num * BDRV_SECTOR_SIZE; + if (qiov) { acb->aio_iov = qiov->iov; acb->aio_niov = qiov->niov; + assert(qiov->size == acb->aio_nbytes); } - acb->aio_nbytes = nb_sectors * 512; - acb->aio_offset = sector_num * 512; trace_paio_submit(acb, opaque, sector_num, nb_sectors, type); pool = aio_get_thread_pool(bdrv_get_aio_context(bs)); -- cgit v1.1