]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
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)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 30 Dec 2020 10:51:37 +0000 (11:51 +0100)
commit c57dd1f2f6a7cd1bb61802344f59ccdc5278c983 upstream

[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>
[sudip: adjust context and use extent_io.h]
Signed-off-by: Sudip Mukherjee <sudipm.mukherjee@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/btrfs/extent-tree.c
fs/btrfs/extent_io.h
fs/btrfs/volumes.c

index 6b7c3e98f62b4011f89cbe23676b6d4265b19470..c0dd839e99b799ce07f5a59050a643b6095fbe7c 100644 (file)
@@ -32,6 +32,7 @@
 #include "block-rsv.h"
 #include "delalloc-space.h"
 #include "block-group.h"
+#include "rcu-string.h"
 
 #undef SCRAMBLE_DELAYED_REFS
 
@@ -5618,6 +5619,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 bc858c8cef0a61a8e5e47aa32fe03928c296ca05..fcf1807cc8dd7b418d4170ea36487c1ee9faa972 100644 (file)
@@ -35,6 +35,8 @@
  */
 #define CHUNK_ALLOCATED EXTENT_DIRTY
 #define CHUNK_TRIMMED   EXTENT_DEFRAG
+#define CHUNK_STATE_MASK                       (CHUNK_ALLOCATED |              \
+                                                CHUNK_TRIMMED)
 
 /*
  * flags for bio submission. The high bits indicate the compression
index 457f8f858a3f01b37140e650572a0e9bfef82968..67ffbe92944c674f595abd97902409cd553cddcf 100644 (file)
@@ -4908,6 +4908,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,