qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Hanna Reitz <hreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>, qemu-devel@nongnu.org
Subject: Re: [PATCH] block: Fix BB.root changing across bdrv_next()
Date: Fri, 11 Mar 2022 16:20:09 +0100	[thread overview]
Message-ID: <37a0d9e9-c72c-3524-1a68-d47967d33dcf@redhat.com> (raw)
In-Reply-To: <20220301173914.12279-1-hreitz@redhat.com>

On 01.03.22 18:39, Hanna Reitz wrote:
> bdrv_next() has no guarantee that its caller has stopped all block graph
> operations; for example, bdrv_flush_all() does not.
>
> The latter can actually provoke such operations, because its
> bdrv_flush() call, which runs a coroutine (bdrv_co_flush()), may run
> this coroutine in a different AioContext than the main one, and then
> when this coroutine is done and invokes aio_wait_kick(), the monitor may
> get a chance to run and start executing some graph-modifying QMP
> command.
>
> One example for this is when the VM encounters an I/O error on a block
> device and stops, triggering a bdrv_flush_all(), and a blockdev-mirror
> is started simultaneously on a block node in an I/O thread.  When
> bdrv_flush_all() comes to that node[1] and flushes it, the
> aio_wait_kick() at the end of bdrv_co_flush_entry() may cause the
> monitor to process the mirror request, and mirror_start_job() will then
> replace the node by a mirror filter node, before bdrv_flush_all()
> resumes and can invoke bdrv_next() again to continue iterating.
>
> [1] Say there is a BlockBackend on top of the node in question, and so
> bdrv_next() finds that BB and returns the node as the BB's blk_bs().
> bdrv_next() will bdrv_ref() the node such that it remains valid through
> bdrv_flush_all()'s iteration, and drop the reference when it is called
> the next time.
>
> The problem is that bdrv_next() does not store to which BDS it retains a
> strong reference when the BDS is a BB's child, so on the subsequent
> call, it will just invoke blk_bs() again and bdrv_unref() the returned
> node -- but as the example above shows, this node might be a different
> one than the one that was bdrv_ref()-ed before.  This can lead to a
> use-after-free (for the mirror filter node in our example), because this
> negligent bdrv_unref() would steal a strong reference from someone else.
>
> We can solve this problem by always storing the returned (and strongly
> referenced) BDS in BdrvNextIterator.bs.  When we want to drop the strong
> reference of a BDS previously returned, always drop BdrvNextIterator.bs
> instead of using other ways of trying to figure out what that BDS was
> that we returned last time.

So a week ago, Kevin and me talked about this on IRC, and he was rather 
apprehensive of this approach, because (1) it fixes a probably 
high-level problem in one specific low-level place, and (2) it’s not 
even quite clear whether even this specific problem is really fixed.

(For (2): If bdrv_next() can cope with graph changes, then if such a 
change occurs during bdrv_flush_all(), it isn’t entirely clear whether 
we’ve truly iterated over all nodes and flushed them all.)

I’ve put a more detailed description of what I think is happening step 
by step here: https://bugzilla.redhat.com/show_bug.cgi?id=2058457#c7

So, the question came up whether we shouldn’t put bdrv_flush_all() into 
a drained section (there is a bdrv_drain_all() before, it’s just not a 
section), and ensure that no QMP commands can be executed in drained 
sections.  I fiddled around a bit, just wanting to send an extremely 
rough RFC to see whether the direction I’d be going in made any sense at 
all, but I’m not really making progress:

I wanted to basically introduce an Rwlock for QMP request processing, 
and take a read lock while we’re in a drained section. That doesn’t work 
so well, though, because when a QMP command (i.e. Rwlock is taken for a 
writer) uses drain (trying to take it as a reader), there’s a deadlock.  
I don’t really have a good idea to consolidate this, because if running 
QMP commands during drains is forbidden, then, well, a QMP command 
cannot use drain.  We’d need some way to identify that the drain is 
based in the currently running QMP command, and allow that, but I really 
don’t know how.


While looking into the STOP behavior further, something else came up.  
Summarizing part of my comment linked above, part of the problem is that 
vm_stop() runs, and concurrently the guest device requests another STOP; 
therefore, the next main loop iteration will try to STOP again.  That 
seems to be why the monitor makes some progress during one main loop 
iteration, and then the next unfortunate sequence of polling can lead to 
the monitor processing a QMP command.

So other things to consider could be whether we should ensure that the 
monitor is not in danger of processing QMP requests when 
main_loop_should_exit() runs, e.g. by polling until it’s back at the top 
of its main loop (in monitor_qmp_dispatcher_co()).

Or whether we could make qemu_system_vmstop_request() prevent double 
stop requests, by forbidding STOP requests while a previous STOP request 
has not yet been completely processed (i.e. accepting another one only 
once the VM has been stopped one time).

The simplest way to fix this very issue I’m seeing at least would be 
just to pull do_vm_stop()’s bdrv_drain_all()+bdrv_flush_all() into its 
conditional path; i.e. to only do this if the VM hadn’t been already 
stopped.  (I don’t think we need to flush here if the VM was already 
stopped before.  Might be wrong, though.)  I doubt that’s a great 
solution, but it’d be simple at least.

Hanna



      reply	other threads:[~2022-03-11 15:22 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-01 17:39 [PATCH] block: Fix BB.root changing across bdrv_next() Hanna Reitz
2022-03-11 15:20 ` Hanna Reitz [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=37a0d9e9-c72c-3524-1a68-d47967d33dcf@redhat.com \
    --to=hreitz@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).