]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
btrfs: tree-checker: reject inline extent items with 0 ref count
authorQu Wenruo <wqu@suse.com>
Wed, 4 Dec 2024 03:00:46 +0000 (13:30 +1030)
committerDavid Sterba <dsterba@suse.com>
Tue, 17 Dec 2024 18:54:32 +0000 (19:54 +0100)
[BUG]
There is a bug report in the mailing list where btrfs_run_delayed_refs()
failed to drop the ref count for logical 25870311358464 num_bytes
2113536.

The involved leaf dump looks like this:

  item 166 key (25870311358464 168 2113536) itemoff 10091 itemsize 50
    extent refs 1 gen 84178 flags 1
    ref#0: shared data backref parent 32399126528000 count 0 <<<
    ref#1: shared data backref parent 31808973717504 count 1

Notice the count number is 0.

[CAUSE]
There is no concrete evidence yet, but considering 0 -> 1 is also a
single bit flipped, it's possible that hardware memory bitflip is
involved, causing the on-disk extent tree to be corrupted.

[FIX]
To prevent us reading such corrupted extent item, or writing such
damaged extent item back to disk, enhance the handling of
BTRFS_EXTENT_DATA_REF_KEY and BTRFS_SHARED_DATA_REF_KEY keys for both
inlined and key items, to detect such 0 ref count and reject them.

CC: stable@vger.kernel.org # 5.4+
Link: https://lore.kernel.org/linux-btrfs/7c69dd49-c346-4806-86e7-e6f863a66f48@app.fastmail.com/
Reported-by: Frankie Fisher <frankie@terrorise.me.uk>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
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 148d8cefa40eef5916afe98aaa2e13f23d41b75b..dfeee033f31fb93e2dfbe42be48926f6056465de 100644 (file)
@@ -1527,6 +1527,11 @@ static int check_extent_item(struct extent_buffer *leaf,
                                           dref_offset, fs_info->sectorsize);
                                return -EUCLEAN;
                        }
+                       if (unlikely(btrfs_extent_data_ref_count(leaf, dref) == 0)) {
+                               extent_err(leaf, slot,
+                       "invalid data ref count, should have non-zero value");
+                               return -EUCLEAN;
+                       }
                        inline_refs += btrfs_extent_data_ref_count(leaf, dref);
                        break;
                /* Contains parent bytenr and ref count */
@@ -1539,6 +1544,11 @@ static int check_extent_item(struct extent_buffer *leaf,
                                           inline_offset, fs_info->sectorsize);
                                return -EUCLEAN;
                        }
+                       if (unlikely(btrfs_shared_data_ref_count(leaf, sref) == 0)) {
+                               extent_err(leaf, slot,
+                       "invalid shared data ref count, should have non-zero value");
+                               return -EUCLEAN;
+                       }
                        inline_refs += btrfs_shared_data_ref_count(leaf, sref);
                        break;
                case BTRFS_EXTENT_OWNER_REF_KEY:
@@ -1611,8 +1621,18 @@ static int check_simple_keyed_refs(struct extent_buffer *leaf,
 {
        u32 expect_item_size = 0;
 
-       if (key->type == BTRFS_SHARED_DATA_REF_KEY)
+       if (key->type == BTRFS_SHARED_DATA_REF_KEY) {
+               struct btrfs_shared_data_ref *sref;
+
+               sref = btrfs_item_ptr(leaf, slot, struct btrfs_shared_data_ref);
+               if (unlikely(btrfs_shared_data_ref_count(leaf, sref) == 0)) {
+                       extent_err(leaf, slot,
+               "invalid shared data backref count, should have non-zero value");
+                       return -EUCLEAN;
+               }
+
                expect_item_size = sizeof(struct btrfs_shared_data_ref);
+       }
 
        if (unlikely(btrfs_item_size(leaf, slot) != expect_item_size)) {
                generic_err(leaf, slot,
@@ -1689,6 +1709,11 @@ static int check_extent_data_ref(struct extent_buffer *leaf,
                                   offset, leaf->fs_info->sectorsize);
                        return -EUCLEAN;
                }
+               if (unlikely(btrfs_extent_data_ref_count(leaf, dref) == 0)) {
+                       extent_err(leaf, slot,
+       "invalid extent data backref count, should have non-zero value");
+                       return -EUCLEAN;
+               }
        }
        return 0;
 }