]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: tree-checker: add more cross checks for free space tree
authorQu Wenruo <wqu@suse.com>
Mon, 11 May 2026 00:56:51 +0000 (10:26 +0930)
committerDavid Sterba <dsterba@suse.com>
Mon, 8 Jun 2026 13:53:33 +0000 (15:53 +0200)
This introduces extra checks using the previous key.

If there is a previous key, we can do extra validations:

- The previous key is FREE_SPACE_INFO
  This means the current extent/bitmap should be inside the
  free space info key range.

  And matches the type of the free space info.

- The previous key is FREE_SPACE_EXTENT or FREE_SPACE_BITMAP
  In that case both the current and previous key should belong to the same
  block group.

  Thus the key type must match, and no overlap between the two keys.

These extra checks are inspired by the recently added type checks during
free space tree loading.

The new tree-checker checks will allow earlier detection, but the
loading time checks are still needed, as the tree-checker checks are
still inside the same leaf, not matching per-bg level checks.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/tree-checker.c

index 5431f71e2a471fd1bb0d6d061f2585cf78f4d362..b7ff3e3d3a63123230ee67b4f515da95816b6c5d 100644 (file)
@@ -2150,7 +2150,8 @@ static int check_free_space_info(struct extent_buffer *leaf, struct btrfs_key *k
        return 0;
 }
 
-static int check_free_space_common_key(struct extent_buffer *leaf, struct btrfs_key *key, int slot)
+static int check_free_space_common_key(struct extent_buffer *leaf, struct btrfs_key *key, int slot,
+                                      struct btrfs_key *prev_key)
 {
        struct btrfs_fs_info *fs_info = leaf->fs_info;
        const u32 blocksize = fs_info->sectorsize;
@@ -2179,14 +2180,65 @@ static int check_free_space_common_key(struct extent_buffer *leaf, struct btrfs_
                            type_str, key->objectid, key->offset);
                return -EUCLEAN;
        }
+       if (slot == 0)
+               return 0;
+
+       /*
+        * Make sure the current key is inside the block group, and matching
+        * the expected info type.
+        */
+       if (prev_key->type == BTRFS_FREE_SPACE_INFO_KEY) {
+               struct btrfs_free_space_info *fsi;
+               u32 info_flags;
+
+               if (unlikely(key->objectid < prev_key->objectid ||
+                            key->objectid + key->offset > prev_key->objectid + prev_key->offset)) {
+                       generic_err(leaf, slot,
+"free space %s is not inside the space info, prev key " BTRFS_KEY_FMT " current key " BTRFS_KEY_FMT,
+                                   type_str, BTRFS_KEY_FMT_VALUE(prev_key),
+                                   BTRFS_KEY_FMT_VALUE(key));
+                       return -EUCLEAN;
+               }
+               fsi = btrfs_item_ptr(leaf, slot - 1, struct btrfs_free_space_info);
+               info_flags = btrfs_free_space_flags(leaf, fsi);
+               if (unlikely((info_flags == BTRFS_FREE_SPACE_USING_BITMAPS &&
+                             key->type == BTRFS_FREE_SPACE_EXTENT_KEY) ||
+                            (info_flags != BTRFS_FREE_SPACE_USING_BITMAPS &&
+                             key->type == BTRFS_FREE_SPACE_BITMAP_KEY))) {
+                       generic_err(leaf, slot,
+"free space %s key type is not matching the type of space info, key type %u space info flags %u",
+                                   type_str, key->type, info_flags);
+                       return -EUCLEAN;
+               }
+               return 0;
+       }
+       /*
+        * Previous key should be either FREE_SPACE_EXTENT or FREE_SPACE_BITMAP.
+        * Inside the same block group the key type should match each other, and
+        * no overlaps.
+        */
+       if (unlikely(key->type != prev_key->type)) {
+               generic_err(leaf, slot,
+"free space %s key type is not matching the type of previous key, key type %u prev key type %u",
+                           type_str, key->type, prev_key->type);
+               return -EUCLEAN;
+       }
+       if (unlikely(prev_key->objectid + prev_key->offset > key->objectid)) {
+               generic_err(leaf, slot,
+"free space %s key overlaps previous key, prev key " BTRFS_KEY_FMT " current key " BTRFS_KEY_FMT,
+                           type_str, BTRFS_KEY_FMT_VALUE(prev_key),
+                           BTRFS_KEY_FMT_VALUE(key));
+               return -EUCLEAN;
+       }
        return 0;
 }
 
-static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key *key, int slot)
+static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key *key, int slot,
+                                  struct btrfs_key *prev_key)
 {
        int ret;
 
-       ret = check_free_space_common_key(leaf, key, slot);
+       ret = check_free_space_common_key(leaf, key, slot, prev_key);
        if (unlikely(ret < 0))
                return ret;
 
@@ -2200,13 +2252,14 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key
 }
 
 static int check_free_space_bitmap(struct extent_buffer *leaf,
-                                  struct btrfs_key *key, int slot)
+                                  struct btrfs_key *key, int slot,
+                                  struct btrfs_key *prev_key)
 {
        struct btrfs_fs_info *fs_info = leaf->fs_info;
        u32 expected_item_size;
        int ret;
 
-       ret = check_free_space_common_key(leaf, key, slot);
+       ret = check_free_space_common_key(leaf, key, slot, prev_key);
        if (unlikely(ret < 0))
                return ret;
 
@@ -2298,10 +2351,10 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
                ret = check_free_space_info(leaf, key, slot);
                break;
        case BTRFS_FREE_SPACE_EXTENT_KEY:
-               ret = check_free_space_extent(leaf, key, slot);
+               ret = check_free_space_extent(leaf, key, slot, prev_key);
                break;
        case BTRFS_FREE_SPACE_BITMAP_KEY:
-               ret = check_free_space_bitmap(leaf, key, slot);
+               ret = check_free_space_bitmap(leaf, key, slot, prev_key);
                break;
        case BTRFS_IDENTITY_REMAP_KEY:
        case BTRFS_REMAP_KEY: