OSDN Git Service

btrfs: trim: fix underflow in trim length to prevent access beyond device boundary
authorQu Wenruo <wqu@suse.com>
Fri, 31 Jul 2020 11:29:11 +0000 (19:29 +0800)
committerDavid Sterba <dsterba@suse.com>
Wed, 12 Aug 2020 08:15:58 +0000 (10:15 +0200)
[BUG]
The following script can lead to tons of beyond device boundary access:

  mkfs.btrfs -f $dev -b 10G
  mount $dev $mnt
  trimfs $mnt
  btrfs filesystem resize 1:-1G $mnt
  trimfs $mnt

[CAUSE]
Since commit 929be17a9b49 ("btrfs: Switch btrfs_trim_free_extents to
find_first_clear_extent_bit"), we try to avoid trimming ranges that's
already trimmed.

So we check device->alloc_state by finding the first range which doesn't
have CHUNK_TRIMMED and CHUNK_ALLOCATED not set.

But if we shrunk the device, that bits are not cleared, thus we could
easily got a range starts beyond the shrunk device size.

This results the returned @start and @end are all beyond device size,
then we call "end = min(end, device->total_bytes -1);" making @end
smaller than device size.

Then finally we goes "len = end - start + 1", totally underflow the
result, and lead to the beyond-device-boundary access.

[FIX]
This patch will fix the problem in two ways:

- Clear CHUNK_TRIMMED | CHUNK_ALLOCATED bits when shrinking device
  This is the root fix

- Add extra safety check when trimming free device extents
  We check and warn if the returned range is already beyond current
  device.

Link: https://github.com/kdave/btrfs-progs/issues/282
Fixes: 929be17a9b49 ("btrfs: Switch btrfs_trim_free_extents to find_first_clear_extent_bit")
CC: stable@vger.kernel.org # 5.4+
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/extent-io-tree.h
fs/btrfs/extent-tree.c
fs/btrfs/volumes.c

index f39d47a..219a09a 100644 (file)
@@ -34,6 +34,8 @@ struct io_failure_record;
  */
 #define CHUNK_ALLOCATED                                EXTENT_DIRTY
 #define CHUNK_TRIMMED                          EXTENT_DEFRAG
+#define CHUNK_STATE_MASK                       (CHUNK_ALLOCATED |              \
+                                                CHUNK_TRIMMED)
 
 enum {
        IO_TREE_FS_PINNED_EXTENTS,
index 61ede33..de6fe17 100644 (file)
@@ -33,6 +33,7 @@
 #include "delalloc-space.h"
 #include "block-group.h"
 #include "discard.h"
+#include "rcu-string.h"
 
 #undef SCRAMBLE_DELAYED_REFS
 
@@ -5668,6 +5669,19 @@ static int btrfs_trim_free_extents(struct btrfs_device *device, u64 *trimmed)
                                            &start, &end,
                                            CHUNK_TRIMMED | CHUNK_ALLOCATED);
 
+               /* Check if there are any CHUNK_* bits left */
+               if (start > device->total_bytes) {
+                       WARN_ON(IS_ENABLED(CONFIG_BTRFS_DEBUG));
+                       btrfs_warn_in_rcu(fs_info,
+"ignoring attempt to trim beyond device size: offset %llu length %llu device %s device size %llu",
+                                         start, end - start + 1,
+                                         rcu_str_deref(device->name),
+                                         device->total_bytes);
+                       mutex_unlock(&fs_info->chunk_mutex);
+                       ret = 0;
+                       break;
+               }
+
                /* Ensure we skip the reserved area in the first 1M */
                start = max_t(u64, start, SZ_1M);
 
index d7670e2..ee96c58 100644 (file)
@@ -4720,6 +4720,10 @@ again:
        }
 
        mutex_lock(&fs_info->chunk_mutex);
+       /* Clear all state bits beyond the shrunk device size */
+       clear_extent_bits(&device->alloc_state, new_size, (u64)-1,
+                         CHUNK_STATE_MASK);
+
        btrfs_device_set_disk_total_bytes(device, new_size);
        if (list_empty(&device->post_commit_list))
                list_add_tail(&device->post_commit_list,