cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

test-bdrv-graph-mod.c (14438B)


      1/*
      2 * Block node graph modifications tests
      3 *
      4 * Copyright (c) 2019-2021 Virtuozzo International GmbH. All rights reserved.
      5 *
      6 * This program is free software; you can redistribute it and/or modify
      7 * it under the terms of the GNU General Public License as published by
      8 * the Free Software Foundation; either version 2 of the License, or
      9 * (at your option) any later version.
     10 *
     11 * This program is distributed in the hope that it will be useful,
     12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 * GNU General Public License for more details.
     15 *
     16 * You should have received a copy of the GNU General Public License
     17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     18 *
     19 */
     20
     21#include "qemu/osdep.h"
     22#include "qapi/error.h"
     23#include "qemu/main-loop.h"
     24#include "block/block_int.h"
     25#include "sysemu/block-backend.h"
     26
     27static BlockDriver bdrv_pass_through = {
     28    .format_name = "pass-through",
     29    .bdrv_child_perm = bdrv_default_perms,
     30};
     31
     32static void no_perm_default_perms(BlockDriverState *bs, BdrvChild *c,
     33                                         BdrvChildRole role,
     34                                         BlockReopenQueue *reopen_queue,
     35                                         uint64_t perm, uint64_t shared,
     36                                         uint64_t *nperm, uint64_t *nshared)
     37{
     38    *nperm = 0;
     39    *nshared = BLK_PERM_ALL;
     40}
     41
     42static BlockDriver bdrv_no_perm = {
     43    .format_name = "no-perm",
     44    .supports_backing = true,
     45    .bdrv_child_perm = no_perm_default_perms,
     46};
     47
     48static void exclusive_write_perms(BlockDriverState *bs, BdrvChild *c,
     49                                  BdrvChildRole role,
     50                                  BlockReopenQueue *reopen_queue,
     51                                  uint64_t perm, uint64_t shared,
     52                                  uint64_t *nperm, uint64_t *nshared)
     53{
     54    *nperm = BLK_PERM_WRITE;
     55    *nshared = BLK_PERM_ALL & ~BLK_PERM_WRITE;
     56}
     57
     58static BlockDriver bdrv_exclusive_writer = {
     59    .format_name = "exclusive-writer",
     60    .bdrv_child_perm = exclusive_write_perms,
     61};
     62
     63static BlockDriverState *no_perm_node(const char *name)
     64{
     65    return bdrv_new_open_driver(&bdrv_no_perm, name, BDRV_O_RDWR, &error_abort);
     66}
     67
     68static BlockDriverState *pass_through_node(const char *name)
     69{
     70    return bdrv_new_open_driver(&bdrv_pass_through, name,
     71                                BDRV_O_RDWR, &error_abort);
     72}
     73
     74static BlockDriverState *exclusive_writer_node(const char *name)
     75{
     76    return bdrv_new_open_driver(&bdrv_exclusive_writer, name,
     77                                BDRV_O_RDWR, &error_abort);
     78}
     79
     80/*
     81 * test_update_perm_tree
     82 *
     83 * When checking node for a possibility to update permissions, it's subtree
     84 * should be correctly checked too. New permissions for each node should be
     85 * calculated and checked in context of permissions of other nodes. If we
     86 * check new permissions of the node only in context of old permissions of
     87 * its neighbors, we can finish up with wrong permission graph.
     88 *
     89 * This test firstly create the following graph:
     90 *                                +--------+
     91 *                                |  root  |
     92 *                                +--------+
     93 *                                    |
     94 *                                    | perm: write, read
     95 *                                    | shared: except write
     96 *                                    v
     97 *  +-------------------+           +----------------+
     98 *  | passtrough filter |---------->|  null-co node  |
     99 *  +-------------------+           +----------------+
    100 *
    101 *
    102 * and then, tries to append filter under node. Expected behavior: fail.
    103 * Otherwise we'll get the following picture, with two BdrvChild'ren, having
    104 * write permission to one node, without actually sharing it.
    105 *
    106 *                     +--------+
    107 *                     |  root  |
    108 *                     +--------+
    109 *                         |
    110 *                         | perm: write, read
    111 *                         | shared: except write
    112 *                         v
    113 *                +-------------------+
    114 *                | passtrough filter |
    115 *                +-------------------+
    116 *                       |   |
    117 *     perm: write, read |   | perm: write, read
    118 *  shared: except write |   | shared: except write
    119 *                       v   v
    120 *                +----------------+
    121 *                |  null co node  |
    122 *                +----------------+
    123 */
    124static void test_update_perm_tree(void)
    125{
    126    int ret;
    127
    128    BlockBackend *root = blk_new(qemu_get_aio_context(),
    129                                 BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ,
    130                                 BLK_PERM_ALL & ~BLK_PERM_WRITE);
    131    BlockDriverState *bs = no_perm_node("node");
    132    BlockDriverState *filter = pass_through_node("filter");
    133
    134    blk_insert_bs(root, bs, &error_abort);
    135
    136    bdrv_attach_child(filter, bs, "child", &child_of_bds,
    137                      BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, &error_abort);
    138
    139    ret = bdrv_append(filter, bs, NULL);
    140    g_assert_cmpint(ret, <, 0);
    141
    142    bdrv_unref(filter);
    143    blk_unref(root);
    144}
    145
    146/*
    147 * test_should_update_child
    148 *
    149 * Test that bdrv_replace_node, and concretely should_update_child
    150 * do the right thing, i.e. not creating loops on the graph.
    151 *
    152 * The test does the following:
    153 * 1. initial graph:
    154 *
    155 *   +------+          +--------+
    156 *   | root |          | filter |
    157 *   +------+          +--------+
    158 *      |                  |
    159 *  root|            target|
    160 *      v                  v
    161 *   +------+          +--------+
    162 *   | node |<---------| target |
    163 *   +------+  backing +--------+
    164 *
    165 * 2. Append @filter above @node. If should_update_child works correctly,
    166 * it understands, that backing child of @target should not be updated,
    167 * as it will create a loop on node graph. Resulting picture should
    168 * be the left one, not the right:
    169 *
    170 *     +------+                            +------+
    171 *     | root |                            | root |
    172 *     +------+                            +------+
    173 *        |                                   |
    174 *    root|                               root|
    175 *        v                                   v
    176 *    +--------+   target                 +--------+   target
    177 *    | filter |--------------+           | filter |--------------+
    178 *    +--------+              |           +--------+              |
    179 *        |                   |               |  ^                v
    180 * backing|                   |        backing|  |           +--------+
    181 *        v                   v               |  +-----------| target |
    182 *     +------+          +--------+           v      backing +--------+
    183 *     | node |<---------| target |        +------+
    184 *     +------+  backing +--------+        | node |
    185 *                                         +------+
    186 *
    187 *    (good picture)                       (bad picture)
    188 *
    189 */
    190static void test_should_update_child(void)
    191{
    192    BlockBackend *root = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
    193    BlockDriverState *bs = no_perm_node("node");
    194    BlockDriverState *filter = no_perm_node("filter");
    195    BlockDriverState *target = no_perm_node("target");
    196
    197    blk_insert_bs(root, bs, &error_abort);
    198
    199    bdrv_set_backing_hd(target, bs, &error_abort);
    200
    201    g_assert(target->backing->bs == bs);
    202    bdrv_attach_child(filter, target, "target", &child_of_bds,
    203                      BDRV_CHILD_DATA, &error_abort);
    204    bdrv_append(filter, bs, &error_abort);
    205    g_assert(target->backing->bs == bs);
    206
    207    bdrv_unref(filter);
    208    bdrv_unref(bs);
    209    blk_unref(root);
    210}
    211
    212/*
    213 * test_parallel_exclusive_write
    214 *
    215 * Check that when we replace node, old permissions of the node being removed
    216 * doesn't break the replacement.
    217 */
    218static void test_parallel_exclusive_write(void)
    219{
    220    BlockDriverState *top = exclusive_writer_node("top");
    221    BlockDriverState *base = no_perm_node("base");
    222    BlockDriverState *fl1 = pass_through_node("fl1");
    223    BlockDriverState *fl2 = pass_through_node("fl2");
    224
    225    /*
    226     * bdrv_attach_child() eats child bs reference, so we need two @base
    227     * references for two filters:
    228     */
    229    bdrv_ref(base);
    230
    231    bdrv_attach_child(top, fl1, "backing", &child_of_bds, BDRV_CHILD_DATA,
    232                      &error_abort);
    233    bdrv_attach_child(fl1, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED,
    234                      &error_abort);
    235    bdrv_attach_child(fl2, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED,
    236                      &error_abort);
    237
    238    bdrv_replace_node(fl1, fl2, &error_abort);
    239
    240    bdrv_unref(fl2);
    241    bdrv_unref(top);
    242}
    243
    244static void write_to_file_perms(BlockDriverState *bs, BdrvChild *c,
    245                                     BdrvChildRole role,
    246                                     BlockReopenQueue *reopen_queue,
    247                                     uint64_t perm, uint64_t shared,
    248                                     uint64_t *nperm, uint64_t *nshared)
    249{
    250    if (bs->file && c == bs->file) {
    251        *nperm = BLK_PERM_WRITE;
    252        *nshared = BLK_PERM_ALL & ~BLK_PERM_WRITE;
    253    } else {
    254        *nperm = 0;
    255        *nshared = BLK_PERM_ALL;
    256    }
    257}
    258
    259static BlockDriver bdrv_write_to_file = {
    260    .format_name = "tricky-perm",
    261    .bdrv_child_perm = write_to_file_perms,
    262};
    263
    264
    265/*
    266 * The following test shows that topological-sort order is required for
    267 * permission update, simple DFS is not enough.
    268 *
    269 * Consider the block driver which has two filter children: one active
    270 * with exclusive write access and one inactive with no specific
    271 * permissions.
    272 *
    273 * And, these two children has a common base child, like this:
    274 *
    275 * ┌─────┐     ┌──────┐
    276 * │ fl2 │ ◀── │ top  │
    277 * └─────┘     └──────┘
    278 *   │           │
    279 *   │           │ w
    280 *   │           ▼
    281 *   │         ┌──────┐
    282 *   │         │ fl1  │
    283 *   │         └──────┘
    284 *   │           │
    285 *   │           │ w
    286 *   │           ▼
    287 *   │         ┌──────┐
    288 *   └───────▶ │ base │
    289 *             └──────┘
    290 *
    291 * So, exclusive write is propagated.
    292 *
    293 * Assume, we want to make fl2 active instead of fl1.
    294 * So, we set some option for top driver and do permission update.
    295 *
    296 * With simple DFS, if permission update goes first through
    297 * top->fl1->base branch it will succeed: it firstly drop exclusive write
    298 * permissions and than apply them for another BdrvChildren.
    299 * But if permission update goes first through top->fl2->base branch it
    300 * will fail, as when we try to update fl2->base child, old not yet
    301 * updated fl1->base child will be in conflict.
    302 *
    303 * With topological-sort order we always update parents before children, so fl1
    304 * and fl2 are both updated when we update base and there is no conflict.
    305 */
    306static void test_parallel_perm_update(void)
    307{
    308    BlockDriverState *top = no_perm_node("top");
    309    BlockDriverState *tricky =
    310            bdrv_new_open_driver(&bdrv_write_to_file, "tricky", BDRV_O_RDWR,
    311                                 &error_abort);
    312    BlockDriverState *base = no_perm_node("base");
    313    BlockDriverState *fl1 = pass_through_node("fl1");
    314    BlockDriverState *fl2 = pass_through_node("fl2");
    315    BdrvChild *c_fl1, *c_fl2;
    316
    317    /*
    318     * bdrv_attach_child() eats child bs reference, so we need two @base
    319     * references for two filters:
    320     */
    321    bdrv_ref(base);
    322
    323    bdrv_attach_child(top, tricky, "file", &child_of_bds, BDRV_CHILD_DATA,
    324                      &error_abort);
    325    c_fl1 = bdrv_attach_child(tricky, fl1, "first", &child_of_bds,
    326                              BDRV_CHILD_FILTERED, &error_abort);
    327    c_fl2 = bdrv_attach_child(tricky, fl2, "second", &child_of_bds,
    328                              BDRV_CHILD_FILTERED, &error_abort);
    329    bdrv_attach_child(fl1, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED,
    330                      &error_abort);
    331    bdrv_attach_child(fl2, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED,
    332                      &error_abort);
    333
    334    /* Select fl1 as first child to be active */
    335    tricky->file = c_fl1;
    336    bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
    337
    338    assert(c_fl1->perm & BLK_PERM_WRITE);
    339    assert(!(c_fl2->perm & BLK_PERM_WRITE));
    340
    341    /* Now, try to switch active child and update permissions */
    342    tricky->file = c_fl2;
    343    bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
    344
    345    assert(c_fl2->perm & BLK_PERM_WRITE);
    346    assert(!(c_fl1->perm & BLK_PERM_WRITE));
    347
    348    /* Switch once more, to not care about real child order in the list */
    349    tricky->file = c_fl1;
    350    bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
    351
    352    assert(c_fl1->perm & BLK_PERM_WRITE);
    353    assert(!(c_fl2->perm & BLK_PERM_WRITE));
    354
    355    bdrv_unref(top);
    356}
    357
    358/*
    359 * It's possible that filter required permissions allows to insert it to backing
    360 * chain, like:
    361 *
    362 *  1.  [top] -> [filter] -> [base]
    363 *
    364 * but doesn't allow to add it as a branch:
    365 *
    366 *  2.  [filter] --\
    367 *                 v
    368 *      [top] -> [base]
    369 *
    370 * So, inserting such filter should do all graph modifications and only then
    371 * update permissions. If we try to go through intermediate state [2] and update
    372 * permissions on it we'll fail.
    373 *
    374 * Let's check that bdrv_append() can append such a filter.
    375 */
    376static void test_append_greedy_filter(void)
    377{
    378    BlockDriverState *top = exclusive_writer_node("top");
    379    BlockDriverState *base = no_perm_node("base");
    380    BlockDriverState *fl = exclusive_writer_node("fl1");
    381
    382    bdrv_attach_child(top, base, "backing", &child_of_bds, BDRV_CHILD_COW,
    383                      &error_abort);
    384
    385    bdrv_append(fl, base, &error_abort);
    386    bdrv_unref(fl);
    387    bdrv_unref(top);
    388}
    389
    390int main(int argc, char *argv[])
    391{
    392    bdrv_init();
    393    qemu_init_main_loop(&error_abort);
    394
    395    g_test_init(&argc, &argv, NULL);
    396
    397    g_test_add_func("/bdrv-graph-mod/update-perm-tree", test_update_perm_tree);
    398    g_test_add_func("/bdrv-graph-mod/should-update-child",
    399                    test_should_update_child);
    400    g_test_add_func("/bdrv-graph-mod/parallel-perm-update",
    401                    test_parallel_perm_update);
    402    g_test_add_func("/bdrv-graph-mod/parallel-exclusive-write",
    403                    test_parallel_exclusive_write);
    404    g_test_add_func("/bdrv-graph-mod/append-greedy-filter",
    405                    test_append_greedy_filter);
    406
    407    return g_test_run();
    408}