Skip to content

Commit

Permalink
block-backend: fix edge case in bdrv_next() where BDS associated to B…
Browse files Browse the repository at this point in the history
…B changes

The old_bs variable in bdrv_next() is currently determined by looking
at the old block backend. However, if the block graph changes before
the next bdrv_next() call, it might be that the associated BDS is not
the same that was referenced previously. In that case, the wrong BDS
is unreferenced, leading to an assertion failure later:

> bdrv_unref: Assertion `bs->refcnt > 0' failed.

In particular, this can happen in the context of bdrv_flush_all(),
when polling for bdrv_co_flush() in the generated co-wrapper leads to
a graph change (for example with a stream block job [0]).

A racy reproducer:

> #!/bin/bash
> rm -f /tmp/backing.qcow2
> rm -f /tmp/top.qcow2
> ./qemu-img create /tmp/backing.qcow2 -f qcow2 64M
> ./qemu-io -c "write -P42 0x0 0x1" /tmp/backing.qcow2
> ./qemu-img create /tmp/top.qcow2 -f qcow2 64M -b /tmp/backing.qcow2 -F qcow2
> ./qemu-system-x86_64 --qmp stdio \
> --blockdev qcow2,node-name=node0,file.driver=file,file.filename=/tmp/top.qcow2 \
> <<EOF
> {"execute": "qmp_capabilities"}
> {"execute": "block-stream", "arguments": { "job-id": "stream0", "device": "node0" } }
> {"execute": "quit"}
> EOF

[0]:

> #0  bdrv_replace_child_tran (child=..., new_bs=..., tran=...)
> #1  bdrv_replace_node_noperm (from=..., to=..., auto_skip=..., tran=..., errp=...)
> qemu#2  bdrv_replace_node_common (from=..., to=..., auto_skip=..., detach_subchain=..., errp=...)
> qemu#3  bdrv_drop_filter (bs=..., errp=...)
> qemu#4  bdrv_cor_filter_drop (cor_filter_bs=...)
> qemu#5  stream_prepare (job=...)
> qemu#6  job_prepare_locked (job=...)
> qemu#7  job_txn_apply_locked (fn=..., job=...)
> qemu#8  job_do_finalize_locked (job=...)
> qemu#9  job_exit (opaque=...)
> qemu#10 aio_bh_poll (ctx=...)
> qemu#11 aio_poll (ctx=..., blocking=...)
> qemu#12 bdrv_poll_co (s=...)
> qemu#13 bdrv_flush (bs=...)
> qemu#14 bdrv_flush_all ()
> qemu#15 do_vm_stop (state=..., send_stop=...)
> qemu#16 vm_shutdown ()

Signed-off-by: Fiona Ebner <[email protected]>
Message-ID: <[email protected]>
Reviewed-by: Kevin Wolf <[email protected]>
Reviewed-by: Stefan Hajnoczi <[email protected]>
Signed-off-by: Kevin Wolf <[email protected]>
  • Loading branch information
foxmox authored and kevmw committed Mar 26, 2024
1 parent 3f93481 commit f6d38c9
Showing 1 changed file with 3 additions and 4 deletions.
7 changes: 3 additions & 4 deletions block/block-backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -599,14 +599,14 @@ BlockDriverState *bdrv_next(BdrvNextIterator *it)
/* Must be called from the main loop */
assert(qemu_get_current_aio_context() == qemu_get_aio_context());

old_bs = it->bs;

/* First, return all root nodes of BlockBackends. In order to avoid
* returning a BDS twice when multiple BBs refer to it, we only return it
* if the BB is the first one in the parent list of the BDS. */
if (it->phase == BDRV_NEXT_BACKEND_ROOTS) {
BlockBackend *old_blk = it->blk;

old_bs = old_blk ? blk_bs(old_blk) : NULL;

do {
it->blk = blk_all_next(it->blk);
bs = it->blk ? blk_bs(it->blk) : NULL;
Expand All @@ -620,11 +620,10 @@ BlockDriverState *bdrv_next(BdrvNextIterator *it)
if (bs) {
bdrv_ref(bs);
bdrv_unref(old_bs);
it->bs = bs;
return bs;
}
it->phase = BDRV_NEXT_MONITOR_OWNED;
} else {
old_bs = it->bs;
}

/* Then return the monitor-owned BDSes without a BB attached. Ignore all
Expand Down

0 comments on commit f6d38c9

Please sign in to comment.