commit: Fix use after free in completion

The final bdrv_set_backing_hd() could be working on already freed nodes
because the commit job drops its references (through BlockBackends) to
both overlay_bs and top already a bit earlier.

One way to trigger the bug is hot unplugging a disk for which
blockdev_mark_auto_del() cancels the block job.

Fix this by taking BDS-level references while we're still using the
nodes.

Cc: qemu-stable@nongnu.org
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: John Snow <jsnow@redhat.com>
This commit is contained in:
Kevin Wolf 2017-06-02 23:04:55 +02:00
parent 49695eeb74
commit 19ebd13ed4

View File

@ -89,6 +89,10 @@ static void commit_complete(BlockJob *job, void *opaque)
int ret = data->ret; int ret = data->ret;
bool remove_commit_top_bs = false; bool remove_commit_top_bs = false;
/* Make sure overlay_bs and top stay around until bdrv_set_backing_hd() */
bdrv_ref(top);
bdrv_ref(overlay_bs);
/* Remove base node parent that still uses BLK_PERM_WRITE/RESIZE before /* Remove base node parent that still uses BLK_PERM_WRITE/RESIZE before
* the normal backing chain can be restored. */ * the normal backing chain can be restored. */
blk_unref(s->base); blk_unref(s->base);
@ -124,6 +128,9 @@ static void commit_complete(BlockJob *job, void *opaque)
if (remove_commit_top_bs) { if (remove_commit_top_bs) {
bdrv_set_backing_hd(overlay_bs, top, &error_abort); bdrv_set_backing_hd(overlay_bs, top, &error_abort);
} }
bdrv_unref(overlay_bs);
bdrv_unref(top);
} }
static void coroutine_fn commit_run(void *opaque) static void coroutine_fn commit_run(void *opaque)