qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>,
	Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>,
	qemu-devel@nongnu.org, Max Reitz <mreitz@redhat.com>
Subject: [Qemu-devel] [PATCH v5 35/42] block: Fix check_to_replace_node()
Date: Thu, 13 Jun 2019 00:09:57 +0200	[thread overview]
Message-ID: <20190612221004.2317-36-mreitz@redhat.com> (raw)
In-Reply-To: <20190612221004.2317-1-mreitz@redhat.com>

Currently, check_to_replace_node() only allows mirror to replace a node
in the chain of the source node, and only if it is the first non-filter
node below the source.  Well, technically, the idea is that you can
exactly replace a quorum child by mirroring from quorum.

This has (probably) two reasons:
(1) We do not want to create loops.
(2) @replaces and @device should have exactly the same content so
    replacing them does not cause visible data to change.

This has two issues:
(1) It is overly restrictive.  It is completely fine for @replaces to be
    a filter.
(2) It is not restrictive enough.  You can create loops with this as
    follows:

$ qemu-img create -f qcow2 /tmp/source.qcow2 64M
$ qemu-system-x86_64 -qmp stdio
{"execute": "qmp_capabilities"}
{"execute": "object-add",
 "arguments": {"qom-type": "throttle-group", "id": "tg0"}}
{"execute": "blockdev-add",
 "arguments": {
     "node-name": "source",
     "driver": "throttle",
     "throttle-group": "tg0",
     "file": {
         "node-name": "filtered",
         "driver": "qcow2",
         "file": {
             "driver": "file",
             "filename": "/tmp/source.qcow2"
         } } } }
{"execute": "drive-mirror",
 "arguments": {
     "job-id": "mirror",
     "device": "source",
     "target": "/tmp/target.qcow2",
     "format": "qcow2",
     "node-name": "target",
     "sync" :"none",
     "replaces": "filtered"
 } }
{"execute": "block-job-complete", "arguments": {"device": "mirror"}}

And qemu crashes because of a stack overflow due to the loop being
created (target's backing file is source, so when it replaces filtered,
it points to itself through source).

(blockdev-mirror can be broken similarly.)

So let us make the checks for the two conditions above explicit, which
makes the whole function exactly as restrictive as it needs to be.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 include/block/block.h |  1 +
 block.c               | 83 +++++++++++++++++++++++++++++++++++++++----
 blockdev.c            | 34 ++++++++++++++++--
 3 files changed, 110 insertions(+), 8 deletions(-)

diff --git a/include/block/block.h b/include/block/block.h
index 7835c5b370..484c0af766 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -404,6 +404,7 @@ bool bdrv_is_first_non_filter(BlockDriverState *candidate);
 
 /* check if a named node can be replaced when doing drive-mirror */
 BlockDriverState *check_to_replace_node(BlockDriverState *parent_bs,
+                                        BlockDriverState *backing_bs,
                                         const char *node_name, Error **errp);
 
 /* async block I/O */
diff --git a/block.c b/block.c
index 59d1d4b2b1..e129869a7e 100644
--- a/block.c
+++ b/block.c
@@ -6142,7 +6142,59 @@ bool bdrv_is_first_non_filter(BlockDriverState *candidate)
     return false;
 }
 
+static bool is_child_of(BlockDriverState *child, BlockDriverState *parent)
+{
+    BdrvChild *c;
+
+    if (!parent) {
+        return false;
+    }
+
+    QLIST_FOREACH(c, &parent->children, next) {
+        if (c->bs == child || is_child_of(child, c->bs)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/*
+ * Return true if there are only filters in [@top, @base).  Note that
+ * this may include quorum (which bdrv_chain_contains() cannot
+ * handle).
+ */
+static bool is_filtered_child(BlockDriverState *top, BlockDriverState *base)
+{
+    BdrvChild *c;
+
+    if (!top) {
+        return false;
+    }
+
+    if (top == base) {
+        return true;
+    }
+
+    if (!top->drv->is_filter) {
+        return false;
+    }
+
+    QLIST_FOREACH(c, &top->children, next) {
+        if (is_filtered_child(c->bs, base)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/*
+ * @parent_bs is mirror's source BDS, @backing_bs is the BDS which
+ * will be attached to the target when mirror completes.
+ */
 BlockDriverState *check_to_replace_node(BlockDriverState *parent_bs,
+                                        BlockDriverState *backing_bs,
                                         const char *node_name, Error **errp)
 {
     BlockDriverState *to_replace_bs = bdrv_find_node(node_name);
@@ -6161,13 +6213,32 @@ BlockDriverState *check_to_replace_node(BlockDriverState *parent_bs,
         goto out;
     }
 
-    /* We don't want arbitrary node of the BDS chain to be replaced only the top
-     * most non filter in order to prevent data corruption.
-     * Another benefit is that this tests exclude backing files which are
-     * blocked by the backing blockers.
+    /*
+     * If to_replace_bs is (recursively) a child of backing_bs,
+     * replacing it may create a loop.  We cannot allow that.
      */
-    if (!bdrv_recurse_is_first_non_filter(parent_bs, to_replace_bs)) {
-        error_setg(errp, "Only top most non filter can be replaced");
+    if (to_replace_bs == backing_bs || is_child_of(to_replace_bs, backing_bs)) {
+        error_setg(errp, "Replacing this node would result in a loop");
+        to_replace_bs = NULL;
+        goto out;
+    }
+
+    /*
+     * Mirror is designed in such a way that when it completes, the
+     * source BDS is seamlessly replaced.  It is therefore not allowed
+     * to replace a BDS where this condition would be violated, as that
+     * would defeat the purpose of mirror and could lead to data
+     * corruption.
+     * Therefore, between parent_bs and to_replace_bs there may be
+     * only filters (and the one on top must be a filter, too), so
+     * their data always stays in sync and mirror can complete and
+     * replace to_replace_bs without any possible corruptions.
+     */
+    if (!is_filtered_child(parent_bs, to_replace_bs) &&
+        !is_filtered_child(to_replace_bs, parent_bs))
+    {
+        error_setg(errp, "The node to be replaced must be connected to the "
+                   "source through filter nodes only");
         to_replace_bs = NULL;
         goto out;
     }
diff --git a/blockdev.c b/blockdev.c
index 5370f3b738..6f9f75327e 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3813,7 +3813,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
     }
 
     if (has_replaces) {
-        BlockDriverState *to_replace_bs;
+        BlockDriverState *to_replace_bs, *backing_bs;
         AioContext *replace_aio_context;
         int64_t bs_size, replace_size;
 
@@ -3823,7 +3823,37 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
             return;
         }
 
-        to_replace_bs = check_to_replace_node(bs, replaces, errp);
+        if (backing_mode == MIRROR_SOURCE_BACKING_CHAIN ||
+            backing_mode == MIRROR_OPEN_BACKING_CHAIN)
+        {
+            /*
+             * While we do not quite know what OPEN_BACKING_CHAIN
+             * (used for mode=existing) will yield, it is probably
+             * best to restrict it exactly like SOURCE_BACKING_CHAIN,
+             * because that is our best guess.
+             */
+            switch (sync) {
+            case MIRROR_SYNC_MODE_FULL:
+                backing_bs = NULL;
+                break;
+
+            case MIRROR_SYNC_MODE_TOP:
+                backing_bs = bdrv_filtered_cow_bs(bdrv_skip_rw_filters(bs));
+                break;
+
+            case MIRROR_SYNC_MODE_NONE:
+                backing_bs = bs;
+                break;
+
+            default:
+                abort();
+            }
+        } else {
+            assert(backing_mode == MIRROR_LEAVE_BACKING_CHAIN);
+            backing_bs = bdrv_filtered_cow_bs(bdrv_skip_rw_filters(target));
+        }
+
+        to_replace_bs = check_to_replace_node(bs, backing_bs, replaces, errp);
         if (!to_replace_bs) {
             return;
         }
-- 
2.21.0



  parent reply	other threads:[~2019-06-12 22:51 UTC|newest]

Thread overview: 113+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-06-12 22:09 [Qemu-devel] [PATCH v5 00/42] block: Deal with filters Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 01/42] block: Mark commit and mirror as filter drivers Max Reitz
2019-06-13 10:47   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 02/42] copy-on-read: Support compressed writes Max Reitz
2019-06-13 10:49   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 03/42] throttle: " Max Reitz
2019-06-13 10:51   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 04/42] block: Add child access functions Max Reitz
2019-06-13 12:15   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 05/42] block: Add chain helper functions Max Reitz
2019-06-13 12:26   ` Vladimir Sementsov-Ogievskiy
2019-06-13 12:33     ` Max Reitz
2019-06-13 12:39       ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 06/42] qcow2: Implement .bdrv_storage_child() Max Reitz
2019-06-13 12:27   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 07/42] block: *filtered_cow_child() for *has_zero_init() Max Reitz
2019-06-13 12:34   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 08/42] block: bdrv_set_backing_hd() is about bs->backing Max Reitz
2019-06-13 12:40   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 09/42] block: Include filters when freezing backing chain Max Reitz
2019-06-13 13:04   ` Vladimir Sementsov-Ogievskiy
2019-06-13 14:05     ` Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 10/42] block: Use CAF in bdrv_is_encrypted() Max Reitz
2019-06-13 13:16   ` Vladimir Sementsov-Ogievskiy
2019-06-13 14:15     ` Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 11/42] block: Add bdrv_supports_compressed_writes() Max Reitz
2019-06-13 13:29   ` Vladimir Sementsov-Ogievskiy
2019-06-13 14:19     ` Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 12/42] block: Use bdrv_filtered_rw* where obvious Max Reitz
2019-06-13 13:37   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 13/42] block: Use CAFs in block status functions Max Reitz
2019-06-14 12:07   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 14/42] block: Use CAFs when working with backing chains Max Reitz
2019-06-14 13:26   ` Vladimir Sementsov-Ogievskiy
2019-06-14 13:50     ` Max Reitz
2019-06-14 14:31       ` Vladimir Sementsov-Ogievskiy
2019-06-14 16:02         ` Max Reitz
2019-06-14 16:39           ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 15/42] block: Re-evaluate backing file handling in reopen Max Reitz
2019-06-14 13:42   ` Vladimir Sementsov-Ogievskiy
2019-06-14 15:52     ` Max Reitz
2019-06-14 16:43       ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 16/42] block: Use child access functions when flushing Max Reitz
2019-06-14 14:01   ` Vladimir Sementsov-Ogievskiy
2019-06-14 15:55     ` Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 17/42] block: Use CAFs in bdrv_refresh_limits() Max Reitz
2019-06-14 15:04   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 18/42] block: Use CAFs in bdrv_refresh_filename() Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 19/42] block: Use CAF in bdrv_co_rw_vmstate() Max Reitz
2019-06-14 15:14   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 20/42] block/snapshot: Fall back to storage child Max Reitz
2019-06-14 15:22   ` Vladimir Sementsov-Ogievskiy
2019-06-14 16:10     ` Max Reitz
2019-06-14 16:47       ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 21/42] block: Use CAFs for debug breakpoints Max Reitz
2019-06-14 15:29   ` Vladimir Sementsov-Ogievskiy
2019-06-14 16:12     ` Max Reitz
2019-06-14 20:28       ` Eric Blake
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 22/42] block: Use CAFs in bdrv_get_allocated_file_size() Max Reitz
2019-06-12 22:17   ` Max Reitz
2019-06-14 15:41   ` Vladimir Sementsov-Ogievskiy
2019-06-14 16:15     ` Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 23/42] blockdev: Use CAF in external_snapshot_prepare() Max Reitz
2019-06-14 15:46   ` Vladimir Sementsov-Ogievskiy
2019-06-14 16:20     ` Max Reitz
2019-06-14 16:58       ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 24/42] block: Use child access functions for QAPI queries Max Reitz
2019-06-18 12:06   ` Vladimir Sementsov-Ogievskiy
2019-06-18 14:22     ` Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 25/42] mirror: Deal with filters Max Reitz
2019-06-18 13:12   ` Vladimir Sementsov-Ogievskiy
2019-06-18 14:47     ` Max Reitz
2019-06-18 14:55       ` Vladimir Sementsov-Ogievskiy
2019-06-18 15:20         ` Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 26/42] backup: " Max Reitz
2019-06-18 13:45   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 27/42] commit: " Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 28/42] stream: " Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 29/42] nbd: Use CAF when looking for dirty bitmap Max Reitz
2019-06-18 13:58   ` Vladimir Sementsov-Ogievskiy
2019-06-18 14:48   ` Eric Blake
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 30/42] qemu-img: Use child access functions Max Reitz
2019-06-19  9:18   ` Vladimir Sementsov-Ogievskiy
2019-06-19 15:49     ` Max Reitz
2019-06-21 13:15       ` Vladimir Sementsov-Ogievskiy
2019-07-24  9:54         ` Vladimir Sementsov-Ogievskiy
2019-07-25 16:34           ` Max Reitz
2019-07-26 13:44             ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 31/42] block: Drop backing_bs() Max Reitz
2019-06-19  9:18   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 32/42] block: Make bdrv_get_cumulative_perm() public Max Reitz
2019-06-19  9:19   ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 33/42] blockdev: Fix active commit choice Max Reitz
2019-06-19  9:31   ` Vladimir Sementsov-Ogievskiy
2019-06-19 15:59     ` Max Reitz
2019-06-21 13:26       ` Vladimir Sementsov-Ogievskiy
2019-07-24  9:54         ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 34/42] block: Inline bdrv_co_block_status_from_*() Max Reitz
2019-06-19  9:34   ` Vladimir Sementsov-Ogievskiy
2019-06-19 16:01     ` Max Reitz
2019-06-19 16:07     ` Max Reitz
2019-06-21 13:39   ` Vladimir Sementsov-Ogievskiy
2019-07-24  9:54     ` Vladimir Sementsov-Ogievskiy
2019-06-12 22:09 ` Max Reitz [this message]
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 36/42] iotests: Add tests for mirror @replaces loops Max Reitz
2019-06-12 22:09 ` [Qemu-devel] [PATCH v5 37/42] block: Leave BDS.backing_file constant Max Reitz
2019-06-12 22:10 ` [Qemu-devel] [PATCH v5 38/42] iotests: Let complete_and_wait() work with commit Max Reitz
2019-06-12 22:10 ` [Qemu-devel] [PATCH v5 39/42] iotests: Add filter commit test cases Max Reitz
2019-06-12 22:10 ` [Qemu-devel] [PATCH v5 40/42] iotests: Add filter mirror " Max Reitz
2019-06-12 22:10 ` [Qemu-devel] [PATCH v5 41/42] iotests: Add test for commit in sub directory Max Reitz
2019-06-12 22:10 ` [Qemu-devel] [PATCH v5 42/42] iotests: Test committing to overridden backing Max Reitz
2019-06-13 15:28 ` [Qemu-devel] [PATCH v5 00/42] block: Deal with filters Vladimir Sementsov-Ogievskiy
2019-06-13 16:12   ` Max Reitz

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=20190612221004.2317-36-mreitz@redhat.com \
    --to=mreitz@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=vsementsov@virtuozzo.com \
    /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).