]> git.hungrycats.org Git - linux/commitdiff
Btrfs, raid56: fix use-after-free problem in the final device replace procedure on...
authorMiao Xie <miaox@cn.fujitsu.com>
Tue, 25 Nov 2014 08:39:28 +0000 (16:39 +0800)
committerZygo Blaxell <zblaxell@serenity.furryterror.org>
Sun, 21 Dec 2014 22:08:31 +0000 (17:08 -0500)
The commit c404e0dc (Btrfs: fix use-after-free in the finishing
procedure of the device replace) fixed a use-after-free problem
which happened when removing the source device at the end of device
replace, but at that time, btrfs didn't support device replace
on raid56, so we didn't fix the problem on the raid56 profile.
Currently, we implemented device replace for raid56, so we need
kick that problem out before we enable that function for raid56.

The fix method is very simple, we just increase the bio per-cpu
counter before we submit a raid56 io, and decrease the counter
when the raid56 io ends.

Signed-off-by: Miao Xie <miaox@cn.fujitsu.com>
(cherry picked from commit 4245215d6a8dba1a51c50533b6667919687c0b89)

fs/btrfs/ctree.h
fs/btrfs/dev-replace.c
fs/btrfs/raid56.c
fs/btrfs/raid56.h
fs/btrfs/scrub.c
fs/btrfs/volumes.c

index fc73e86235e8882dc43682563a6a75d4c1d93fef..3770f4c8eced7716e5ad25eed463827d4727b6fe 100644 (file)
@@ -4156,7 +4156,12 @@ int btrfs_scrub_progress(struct btrfs_root *root, u64 devid,
 /* dev-replace.c */
 void btrfs_bio_counter_inc_blocked(struct btrfs_fs_info *fs_info);
 void btrfs_bio_counter_inc_noblocked(struct btrfs_fs_info *fs_info);
-void btrfs_bio_counter_dec(struct btrfs_fs_info *fs_info);
+void btrfs_bio_counter_sub(struct btrfs_fs_info *fs_info, s64 amount);
+
+static inline void btrfs_bio_counter_dec(struct btrfs_fs_info *fs_info)
+{
+       btrfs_bio_counter_sub(fs_info, 1);
+}
 
 /* reada.c */
 struct reada_control {
index 3fbd0628620b745ce474c93d3fc498fe140762ae..8df728ce094064f652cd0312b522092411bf6c49 100644 (file)
@@ -927,9 +927,9 @@ void btrfs_bio_counter_inc_noblocked(struct btrfs_fs_info *fs_info)
        percpu_counter_inc(&fs_info->bio_counter);
 }
 
-void btrfs_bio_counter_dec(struct btrfs_fs_info *fs_info)
+void btrfs_bio_counter_sub(struct btrfs_fs_info *fs_info, s64 amount)
 {
-       percpu_counter_dec(&fs_info->bio_counter);
+       percpu_counter_sub(&fs_info->bio_counter, amount);
 
        if (waitqueue_active(&fs_info->replace_wait))
                wake_up(&fs_info->replace_wait);
index 5ece565bc5f0cda1c38f4ad319e8fb52e24eacba..8ab2a17bbba8b754bdcf90721d3ca40fc0e2f4b6 100644 (file)
@@ -162,6 +162,8 @@ struct btrfs_raid_bio {
         */
        int bio_list_bytes;
 
+       int generic_bio_cnt;
+
        atomic_t refs;
 
        atomic_t stripes_pending;
@@ -354,6 +356,7 @@ static void merge_rbio(struct btrfs_raid_bio *dest,
 {
        bio_list_merge(&dest->bio_list, &victim->bio_list);
        dest->bio_list_bytes += victim->bio_list_bytes;
+       dest->generic_bio_cnt += victim->generic_bio_cnt;
        bio_list_init(&victim->bio_list);
 }
 
@@ -891,6 +894,10 @@ static void rbio_orig_end_io(struct btrfs_raid_bio *rbio, int err, int uptodate)
 {
        struct bio *cur = bio_list_get(&rbio->bio_list);
        struct bio *next;
+
+       if (rbio->generic_bio_cnt)
+               btrfs_bio_counter_sub(rbio->fs_info, rbio->generic_bio_cnt);
+
        free_raid_bio(rbio);
 
        while (cur) {
@@ -1775,6 +1782,7 @@ int raid56_parity_write(struct btrfs_root *root, struct bio *bio,
        struct btrfs_raid_bio *rbio;
        struct btrfs_plug_cb *plug = NULL;
        struct blk_plug_cb *cb;
+       int ret;
 
        rbio = alloc_rbio(root, bbio, raid_map, stripe_len);
        if (IS_ERR(rbio)) {
@@ -1785,12 +1793,19 @@ int raid56_parity_write(struct btrfs_root *root, struct bio *bio,
        rbio->bio_list_bytes = bio->bi_iter.bi_size;
        rbio->operation = BTRFS_RBIO_WRITE;
 
+       btrfs_bio_counter_inc_noblocked(root->fs_info);
+       rbio->generic_bio_cnt = 1;
+
        /*
         * don't plug on full rbios, just get them out the door
         * as quickly as we can
         */
-       if (rbio_is_full(rbio))
-               return full_stripe_write(rbio);
+       if (rbio_is_full(rbio)) {
+               ret = full_stripe_write(rbio);
+               if (ret)
+                       btrfs_bio_counter_dec(root->fs_info);
+               return ret;
+       }
 
        cb = blk_check_plugged(btrfs_raid_unplug, root->fs_info,
                               sizeof(*plug));
@@ -1801,10 +1816,13 @@ int raid56_parity_write(struct btrfs_root *root, struct bio *bio,
                        INIT_LIST_HEAD(&plug->rbio_list);
                }
                list_add_tail(&rbio->plug_list, &plug->rbio_list);
+               ret = 0;
        } else {
-               return __raid56_parity_write(rbio);
+               ret = __raid56_parity_write(rbio);
+               if (ret)
+                       btrfs_bio_counter_dec(root->fs_info);
        }
-       return 0;
+       return ret;
 }
 
 /*
@@ -2139,19 +2157,17 @@ cleanup:
  */
 int raid56_parity_recover(struct btrfs_root *root, struct bio *bio,
                          struct btrfs_bio *bbio, u64 *raid_map,
-                         u64 stripe_len, int mirror_num, int hold_bbio)
+                         u64 stripe_len, int mirror_num, int generic_io)
 {
        struct btrfs_raid_bio *rbio;
        int ret;
 
        rbio = alloc_rbio(root, bbio, raid_map, stripe_len);
        if (IS_ERR(rbio)) {
-               __free_bbio_and_raid_map(bbio, raid_map, !hold_bbio);
+               __free_bbio_and_raid_map(bbio, raid_map, generic_io);
                return PTR_ERR(rbio);
        }
 
-       if (hold_bbio)
-               set_bit(RBIO_HOLD_BBIO_MAP_BIT, &rbio->flags);
        rbio->operation = BTRFS_RBIO_READ_REBUILD;
        bio_list_add(&rbio->bio_list, bio);
        rbio->bio_list_bytes = bio->bi_iter.bi_size;
@@ -2159,11 +2175,18 @@ int raid56_parity_recover(struct btrfs_root *root, struct bio *bio,
        rbio->faila = find_logical_bio_stripe(rbio, bio);
        if (rbio->faila == -1) {
                BUG();
-               __free_bbio_and_raid_map(bbio, raid_map, !hold_bbio);
+               __free_bbio_and_raid_map(bbio, raid_map, generic_io);
                kfree(rbio);
                return -EIO;
        }
 
+       if (generic_io) {
+               btrfs_bio_counter_inc_noblocked(root->fs_info);
+               rbio->generic_bio_cnt = 1;
+       } else {
+               set_bit(RBIO_HOLD_BBIO_MAP_BIT, &rbio->flags);
+       }
+
        /*
         * reconstruct from the q stripe if they are
         * asking for mirror 3
index 3d4ddb3d861d8cbbf51bf099118a95cbbdf9f8da..31d4a157b5e3a153fb283a12e8852a962a63f1f2 100644 (file)
@@ -43,8 +43,8 @@ struct btrfs_raid_bio;
 struct btrfs_device;
 
 int raid56_parity_recover(struct btrfs_root *root, struct bio *bio,
-                                struct btrfs_bio *bbio, u64 *raid_map,
-                                u64 stripe_len, int mirror_num, int hold_bbio);
+                         struct btrfs_bio *bbio, u64 *raid_map,
+                         u64 stripe_len, int mirror_num, int generic_io);
 int raid56_parity_write(struct btrfs_root *root, struct bio *bio,
                               struct btrfs_bio *bbio, u64 *raid_map,
                               u64 stripe_len);
index 7c3cce262c706614f691fb5935faea7b429abb2c..f2bb13a23f860ea19d0403057395d38d8b9d2632 100644 (file)
@@ -1477,7 +1477,7 @@ static int scrub_submit_raid56_bio_wait(struct btrfs_fs_info *fs_info,
        ret = raid56_parity_recover(fs_info->fs_root, bio, page->recover->bbio,
                                    page->recover->raid_map,
                                    page->recover->map_length,
-                                   page->mirror_num, 1);
+                                   page->mirror_num, 0);
        if (ret)
                return ret;
 
index 0f1a06ebd719a0d047ec8a5699f7af9adbe09b1d..25cd53da04babb9f80c1bf687e852b3df0209a2f 100644 (file)
@@ -5849,12 +5849,9 @@ int btrfs_map_bio(struct btrfs_root *root, int rw, struct bio *bio,
                } else {
                        ret = raid56_parity_recover(root, bio, bbio,
                                                    raid_map, map_length,
-                                                   mirror_num, 0);
+                                                   mirror_num, 1);
                }
-               /*
-                * FIXME, replace dosen't support raid56 yet, please fix
-                * it in the future.
-                */
+
                btrfs_bio_counter_dec(root->fs_info);
                return ret;
        }