diff mbox series

btrfs: tree-checker: validate dref root and objectid

Message ID 2b16a2e2f704d80edfb0f52ed91ba50fbd39dbaa.1721031688.git.wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs: tree-checker: validate dref root and objectid | expand

Commit Message

Qu Wenruo July 15, 2024, 8:21 a.m. UTC
[CORRUPTION]
There is a bug report that btrfs flips RO due to a corruption in the
extent tree, the involved dumps looks like this:

 	item 188 key (402811572224 168 4096) itemoff 14598 itemsize 79
 		extent refs 3 gen 3678544 flags 1
 		ref#0: extent data backref root 13835058055282163977 objectid 281473384125923 offset 81432576 count 1
 		ref#1: shared data backref parent 1947073626112 count 1
 		ref#2: shared data backref parent 1156030103552 count 1
 BTRFS critical (device vdc1: state EA): unable to find ref byte nr 402811572224 parent 0 root 265 owner 28703026 offset 81432576 slot 189
 BTRFS error (device vdc1: state EA): failed to run delayed ref for logical 402811572224 num_bytes 4096 type 178 action 2 ref_mod 1: -2

[CAUSE]
The corrupted entry is ref#0 of item 188.
The root number 13835058055282163977 is beyond the upper limit for root
items (the current limit is 1 << 48), and the objectid also looks
suspicious.

Only the offset and count is correct.

[ENHANCEMENT]
Although it's still unknown why we have such many bytes corrupted
randomly, we can still enhance the tree-checker for data backrefs by:

- Validate the root value
  For now there should only be 3 types of roots can have data backref:
  * subvolume trees
  * data reloc trees
  * root tree
    Only for v1 space cache

- validate the objectid value
  The objectid should be a valid inode number.

Hopefully we can catch such problem in the future with the new checkers.

Reported-by: Kai Krakow <hurikhan77@gmail.com>
Link: https://lore.kernel.org/linux-btrfs/CAMthOuPjg5RDT-G_LXeBBUUtzt3cq=JywF+D1_h+JYxe=WKp-Q@mail.gmail.com/#t
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 fs/btrfs/tree-checker.c | 47 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

Comments

Filipe Manana July 15, 2024, 11:02 a.m. UTC | #1
On Mon, Jul 15, 2024 at 9:23 AM Qu Wenruo <wqu@suse.com> wrote:
>
> [CORRUPTION]
> There is a bug report that btrfs flips RO due to a corruption in the
> extent tree, the involved dumps looks like this:
>
>         item 188 key (402811572224 168 4096) itemoff 14598 itemsize 79
>                 extent refs 3 gen 3678544 flags 1
>                 ref#0: extent data backref root 13835058055282163977 objectid 281473384125923 offset 81432576 count 1
>                 ref#1: shared data backref parent 1947073626112 count 1
>                 ref#2: shared data backref parent 1156030103552 count 1
>  BTRFS critical (device vdc1: state EA): unable to find ref byte nr 402811572224 parent 0 root 265 owner 28703026 offset 81432576 slot 189
>  BTRFS error (device vdc1: state EA): failed to run delayed ref for logical 402811572224 num_bytes 4096 type 178 action 2 ref_mod 1: -2
>
> [CAUSE]
> The corrupted entry is ref#0 of item 188.
> The root number 13835058055282163977 is beyond the upper limit for root
> items (the current limit is 1 << 48), and the objectid also looks
> suspicious.
>
> Only the offset and count is correct.
>
> [ENHANCEMENT]
> Although it's still unknown why we have such many bytes corrupted
> randomly, we can still enhance the tree-checker for data backrefs by:
>
> - Validate the root value
>   For now there should only be 3 types of roots can have data backref:
>   * subvolume trees
>   * data reloc trees
>   * root tree
>     Only for v1 space cache
>
> - validate the objectid value
>   The objectid should be a valid inode number.
>
> Hopefully we can catch such problem in the future with the new checkers.
>
> Reported-by: Kai Krakow <hurikhan77@gmail.com>
> Link: https://lore.kernel.org/linux-btrfs/CAMthOuPjg5RDT-G_LXeBBUUtzt3cq=JywF+D1_h+JYxe=WKp-Q@mail.gmail.com/#t
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
>  fs/btrfs/tree-checker.c | 47 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 47 insertions(+)
>
> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> index 6388786fd8b5..d9d0f1f91feb 100644
> --- a/fs/btrfs/tree-checker.c
> +++ b/fs/btrfs/tree-checker.c
> @@ -1289,6 +1289,19 @@ static void extent_err(const struct extent_buffer *eb, int slot,
>         va_end(args);
>  }
>
> +static int is_valid_dref_root(u64 rootid)

This only needs to return true or false, so use a bool type please.

> +{
> +       /*
> +        * The following tree root objectid are allowed to have a data backref:

objectid -> objectids (plural)

Everything else looks fine, so:

Reviewed-by: Filipe Manana <fdmanana@suse.com>

> +        * - subvolume trees
> +        * - data reloc tree
> +        * - tree root
> +        *   For v1 space cache
> +        */
> +       return is_fstree(rootid) || rootid == BTRFS_DATA_RELOC_TREE_OBJECTID ||
> +              rootid == BTRFS_ROOT_TREE_OBJECTID;
> +}
> +
>  static int check_extent_item(struct extent_buffer *leaf,
>                              struct btrfs_key *key, int slot,
>                              struct btrfs_key *prev_key)
> @@ -1441,6 +1454,8 @@ static int check_extent_item(struct extent_buffer *leaf,
>                 struct btrfs_extent_data_ref *dref;
>                 struct btrfs_shared_data_ref *sref;
>                 u64 seq;
> +               u64 dref_root;
> +               u64 dref_objectid;
>                 u64 dref_offset;
>                 u64 inline_offset;
>                 u8 inline_type;
> @@ -1484,11 +1499,26 @@ static int check_extent_item(struct extent_buffer *leaf,
>                  */
>                 case BTRFS_EXTENT_DATA_REF_KEY:
>                         dref = (struct btrfs_extent_data_ref *)(&iref->offset);
> +                       dref_root = btrfs_extent_data_ref_root(leaf, dref);
> +                       dref_objectid = btrfs_extent_data_ref_objectid(leaf, dref);
>                         dref_offset = btrfs_extent_data_ref_offset(leaf, dref);
>                         seq = hash_extent_data_ref(
>                                         btrfs_extent_data_ref_root(leaf, dref),
>                                         btrfs_extent_data_ref_objectid(leaf, dref),
>                                         btrfs_extent_data_ref_offset(leaf, dref));
> +                       if (unlikely(!is_valid_dref_root(dref_root))) {
> +                               extent_err(leaf, slot,
> +                                          "invalid data ref root value %llu",
> +                                          dref_root);
> +                               return -EUCLEAN;
> +                       }
> +                       if (unlikely(dref_objectid < BTRFS_FIRST_FREE_OBJECTID ||
> +                                    dref_objectid > BTRFS_LAST_FREE_OBJECTID)) {
> +                               extent_err(leaf, slot,
> +                                          "invalid data ref objectid value %llu",
> +                                          dref_root);
> +                               return -EUCLEAN;
> +                       }
>                         if (unlikely(!IS_ALIGNED(dref_offset,
>                                                  fs_info->sectorsize))) {
>                                 extent_err(leaf, slot,
> @@ -1627,6 +1657,8 @@ static int check_extent_data_ref(struct extent_buffer *leaf,
>                 return -EUCLEAN;
>         }
>         for (; ptr < end; ptr += sizeof(*dref)) {
> +               u64 root;
> +               u64 objectid;
>                 u64 offset;
>
>                 /*
> @@ -1634,7 +1666,22 @@ static int check_extent_data_ref(struct extent_buffer *leaf,
>                  * overflow from the leaf due to hash collisions.
>                  */
>                 dref = (struct btrfs_extent_data_ref *)ptr;
> +               root = btrfs_extent_data_ref_root(leaf, dref);
> +               objectid = btrfs_extent_data_ref_objectid(leaf, dref);
>                 offset = btrfs_extent_data_ref_offset(leaf, dref);
> +               if (unlikely(!is_valid_dref_root(root))) {
> +                       extent_err(leaf, slot,
> +                                  "invalid extent data backref root value %llu",
> +                                  root);
> +                       return -EUCLEAN;
> +               }
> +               if (unlikely(objectid < BTRFS_FIRST_FREE_OBJECTID ||
> +                            objectid > BTRFS_LAST_FREE_OBJECTID)) {
> +                       extent_err(leaf, slot,
> +                                  "invalid extent data backref objectid value %llu",
> +                                  root);
> +                       return -EUCLEAN;
> +               }
>                 if (unlikely(!IS_ALIGNED(offset, leaf->fs_info->sectorsize))) {
>                         extent_err(leaf, slot,
>         "invalid extent data backref offset, have %llu expect aligned to %u",
> --
> 2.45.2
>
>
diff mbox series

Patch

diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 6388786fd8b5..d9d0f1f91feb 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -1289,6 +1289,19 @@  static void extent_err(const struct extent_buffer *eb, int slot,
 	va_end(args);
 }
 
+static int is_valid_dref_root(u64 rootid)
+{
+	/*
+	 * The following tree root objectid are allowed to have a data backref:
+	 * - subvolume trees
+	 * - data reloc tree
+	 * - tree root
+	 *   For v1 space cache
+	 */
+	return is_fstree(rootid) || rootid == BTRFS_DATA_RELOC_TREE_OBJECTID ||
+	       rootid == BTRFS_ROOT_TREE_OBJECTID;
+}
+
 static int check_extent_item(struct extent_buffer *leaf,
 			     struct btrfs_key *key, int slot,
 			     struct btrfs_key *prev_key)
@@ -1441,6 +1454,8 @@  static int check_extent_item(struct extent_buffer *leaf,
 		struct btrfs_extent_data_ref *dref;
 		struct btrfs_shared_data_ref *sref;
 		u64 seq;
+		u64 dref_root;
+		u64 dref_objectid;
 		u64 dref_offset;
 		u64 inline_offset;
 		u8 inline_type;
@@ -1484,11 +1499,26 @@  static int check_extent_item(struct extent_buffer *leaf,
 		 */
 		case BTRFS_EXTENT_DATA_REF_KEY:
 			dref = (struct btrfs_extent_data_ref *)(&iref->offset);
+			dref_root = btrfs_extent_data_ref_root(leaf, dref);
+			dref_objectid = btrfs_extent_data_ref_objectid(leaf, dref);
 			dref_offset = btrfs_extent_data_ref_offset(leaf, dref);
 			seq = hash_extent_data_ref(
 					btrfs_extent_data_ref_root(leaf, dref),
 					btrfs_extent_data_ref_objectid(leaf, dref),
 					btrfs_extent_data_ref_offset(leaf, dref));
+			if (unlikely(!is_valid_dref_root(dref_root))) {
+				extent_err(leaf, slot,
+					   "invalid data ref root value %llu",
+					   dref_root);
+				return -EUCLEAN;
+			}
+			if (unlikely(dref_objectid < BTRFS_FIRST_FREE_OBJECTID ||
+				     dref_objectid > BTRFS_LAST_FREE_OBJECTID)) {
+				extent_err(leaf, slot,
+					   "invalid data ref objectid value %llu",
+					   dref_root);
+				return -EUCLEAN;
+			}
 			if (unlikely(!IS_ALIGNED(dref_offset,
 						 fs_info->sectorsize))) {
 				extent_err(leaf, slot,
@@ -1627,6 +1657,8 @@  static int check_extent_data_ref(struct extent_buffer *leaf,
 		return -EUCLEAN;
 	}
 	for (; ptr < end; ptr += sizeof(*dref)) {
+		u64 root;
+		u64 objectid;
 		u64 offset;
 
 		/*
@@ -1634,7 +1666,22 @@  static int check_extent_data_ref(struct extent_buffer *leaf,
 		 * overflow from the leaf due to hash collisions.
 		 */
 		dref = (struct btrfs_extent_data_ref *)ptr;
+		root = btrfs_extent_data_ref_root(leaf, dref);
+		objectid = btrfs_extent_data_ref_objectid(leaf, dref);
 		offset = btrfs_extent_data_ref_offset(leaf, dref);
+		if (unlikely(!is_valid_dref_root(root))) {
+			extent_err(leaf, slot,
+				   "invalid extent data backref root value %llu",
+				   root);
+			return -EUCLEAN;
+		}
+		if (unlikely(objectid < BTRFS_FIRST_FREE_OBJECTID ||
+			     objectid > BTRFS_LAST_FREE_OBJECTID)) {
+			extent_err(leaf, slot,
+				   "invalid extent data backref objectid value %llu",
+				   root);
+			return -EUCLEAN;
+		}
 		if (unlikely(!IS_ALIGNED(offset, leaf->fs_info->sectorsize))) {
 			extent_err(leaf, slot,
 	"invalid extent data backref offset, have %llu expect aligned to %u",