Skip to contentSkip to conversation

btrfs: add more superblock checks

PATCH Qu Wenruo 2 participants 2 messages Oct 7, 2014 – Oct 9, 2014 1 message not in the archive Btrfs file system

lore
Share & tools
lore

Qu Wenruo

author
#1 lore raw
In reply to a message not in the archive

Previous reply seems failed to be delivered.

Resend.

-------- 转发的消息 --------
主题: Re: [PATCH] btrfs: add more superblock checks
日期: Tue, 07 Oct 2014 16:44:07 +0800
发件人: Qu Wenruo <[email protected]>
收件人: David Sterba <[email protected]>, [email protected]

Thanks a lot for the patch to deal with the broken image.

Also this seems more extendable.

Small comment inlined below.

-------- Original Message --------
Subject: [PATCH] btrfs: add more superblock checks
From: David Sterba <[email protected]>
To: <[email protected]>
Date: 2014年10月01日 01:16

Quoted 46 lines

Populate btrfs_check_super_valid() with checks that try to verify consistency of superblock by additional conditions that may arise from corrupted devices or bitflips. Some of tests are only hints and issue warnings instead of failing the mount, basically when the checks are derived from the data found in the superblock.

Tested on a broken image provided by Qu.

---
  fs/btrfs/disk-io.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
  1 file changed, 65 insertions(+), 2 deletions(-)
fs/btrfs/disk-io.c +21 -1
index a1d36e62179c..bfb00cb1c84c 100644
@@ -3814,10 +3814,73 @@ int btrfs_read_buffer(struct extent_buffer *buf, u64 parent_transid)
  static int btrfs_check_super_valid(struct btrfs_fs_info *fs_info,
  			      int read_only)
  {
+	struct btrfs_super_block *sb = fs_info->super_copy;
+	int ret = 0;
+
+	if (sb->root_level > BTRFS_MAX_LEVEL) {
+		printk(KERN_ERR "BTRFS: tree_root level too big: %d > %d\n",
+				sb->root_level, BTRFS_MAX_LEVEL);
+		ret = -EINVAL;
+	}
+	if (sb->chunk_root_level > BTRFS_MAX_LEVEL) {
+		printk(KERN_ERR "BTRFS: chunk_root level too big: %d > %d\n",
+				sb->chunk_root_level, BTRFS_MAX_LEVEL);
+		ret = -EINVAL;
+	}
+	if (sb->log_root_level > BTRFS_MAX_LEVEL) {
+		printk(KERN_ERR "BTRFS: log_root level too big: %d > %d\n",
+				sb->log_root_level, BTRFS_MAX_LEVEL);
+		ret = -EINVAL;
+	}
+
  	/*
-	 * Placeholder for checks
+	 * The common minimum, we don't know if we can trust the nodesize/sectorsize
+	 * items yet, they'll be verified later. Issue just a warning.
  	 */

fs/btrfs/disk-io.c int btrfs_read_buffer(struct extent_buffer *buf, u64 parent_transid) · line 3848

Quoted 10 lines
fs/btrfs/disk-io.c +9 -1
-	return 0;
+	if (!IS_ALIGNED(sb->root, 4096))
+		printk(KERN_WARNING "BTRFS: tree_root block unaligned: %llu\n",
+				sb->root);
+	if (!IS_ALIGNED(sb->chunk_root, 4096))
+		printk(KERN_WARNING "BTRFS: tree_root block unaligned: %llu\n",
+				sb->chunk_root);
+	if (!IS_ALIGNED(sb->log_root, 4096))
+		printk(KERN_WARNING "BTRFS: tree_root block unaligned: %llu\n",
+				sb->log_root);

1) it is better not to call IS_ALIGNED to immediate value//.
Although current btrfs implement ensures that all sectorsize is larger equal than page_size, but Chandan Rajendra is trying to support subpage-sized blocksize,
which may cause false alert later.

It would be much better using btrfs_super_sectorsize() instead to improve extendability.

2) missing endian convert.
On big endian system it would be a disaster....
btrfs_super_* marco should be used.

Quoted 4 lines
fs/btrfs/disk-io.c +4 -0
+
+	if (memcmp(fs_info->fsid, sb->dev_item.fsid, BTRFS_UUID_SIZE) != 0) {
+		printk(KERN_ERR "BTRFS: dev_item UUID does not match fsid: %pU != %pU\n",
+				fs_info->fsid, sb->dev_item.fsid);

fs/btrfs/disk-io.c int btrfs_read_buffer(struct extent_buffer *buf, u64 parent_transid) · line 3862

Quoted 10 lines
fs/btrfs/disk-io.c +10 -0
+		ret = -EINVAL;
+	}
+
+	/*
+	 * Hint to catch really bogus numbers, bitflips or so, more exact checks are
+	 * done later
+	 */
+	if (sb->num_devices > (1UL << 31))
+		printk(KERN_WARNING "BTRFS: suspicious number of devices: %llu\n",
+				sb->num_devices);

What about also check the devid with sb->num_devices too? Every valid devid should be less equal than sb->num_devices if I am right. Although iterate dev_item here may be overkilled...

Quoted 13 lines
fs/btrfs/disk-io.c +13 -0
+
+	if (sb->bytenr != BTRFS_SUPER_INFO_OFFSET) {
+		printk(KERN_ERR "BTRFS: super offset mismatch %llu != %u\n",
+				sb->bytenr, BTRFS_SUPER_INFO_OFFSET);
+		ret = -EINVAL;
+	}
+
+	/*
+	 * The generation is a global counter, we'll trust it more than the others
+	 * but it's still possible that it's the one that's wrong.
+	 */
+	if (sb->generation < sb->chunk_root_generation)
+		printk(KERN_WARNING

fs/btrfs/disk-io.c int btrfs_read_buffer(struct extent_buffer *buf, u64 parent_transid) · line 3885

Quoted 10 lines
fs/btrfs/disk-io.c +8 -0
+			"BTRFS: suspicious: generation < chunk_root_generation: %llu < %llu\n",
+			sb->generation, sb->chunk_root_generation);
+	if (sb->generation < sb->cache_generation && sb->cache_generation != (u64)-1)
+		printk(KERN_WARNING
+			"BTRFS: suspicious: generation < cache_generation: %llu < %llu\n",
+			sb->generation, sb->cache_generation);
+
+	return ret;
  }
 

Still the endian problem.

Thanks,
Qu

Quoted 1 line
fs/btrfs/disk-io.c +0 -0
  static void btrfs_error_commit_super(struct btrfs_root *root)
#2 lore raw
Quoted 31 lines · Qu Wenruo
Quoted 31 lines
(quoted diff) +27 -2
+	struct btrfs_super_block *sb = fs_info->super_copy;
+	int ret = 0;
+
+	if (sb->root_level > BTRFS_MAX_LEVEL) {
+		printk(KERN_ERR "BTRFS: tree_root level too big: %d > %d\n",
+				sb->root_level, BTRFS_MAX_LEVEL);
+		ret = -EINVAL;
+	}
+	if (sb->chunk_root_level > BTRFS_MAX_LEVEL) {
+		printk(KERN_ERR "BTRFS: chunk_root level too big: %d > %d\n",
+				sb->chunk_root_level, BTRFS_MAX_LEVEL);
+		ret = -EINVAL;
+	}
+	if (sb->log_root_level > BTRFS_MAX_LEVEL) {
+		printk(KERN_ERR "BTRFS: log_root level too big: %d > %d\n",
+				sb->log_root_level, BTRFS_MAX_LEVEL);
+		ret = -EINVAL;
+	}
+
  	/*
-	 * Placeholder for checks
+	 * The common minimum, we don't know if we can trust the nodesize/sectorsize
+	 * items yet, they'll be verified later. Issue just a warning.
  	 */
-	return 0;
+	if (!IS_ALIGNED(sb->root, 4096))
+		printk(KERN_WARNING "BTRFS: tree_root block unaligned: %llu\n",
+				sb->root);
+	if (!IS_ALIGNED(sb->chunk_root, 4096))
+		printk(KERN_WARNING "BTRFS: tree_root block unaligned: %llu\n",
+				sb->chunk_root);
Quoted 8 lines

Review

Quoted 3 lines
(quoted diff) +3 -0
+	if (!IS_ALIGNED(sb->log_root, 4096))
+		printk(KERN_WARNING "BTRFS: tree_root block unaligned: %llu\n",
+				sb->log_root);

1) it is better not to call IS_ALIGNED to immediate value//. Although current btrfs implement ensures that all sectorsize is larger equal than page_size,
but Chandan Rajendra is trying to support subpage-sized blocksize, which may cause false alert later.

The patch reflects current state, so when the subpage blocksize patches are merged, this will have to be changed accordingly.

Quoted 2 lines

It would be much better using btrfs_super_sectorsize() instead to improve extendability.

See the comment above, we don't trust the superblock yet and cannot use the sectorsize reliably.

Quoted 3 lines

2) missing endian convert.
On big endian system it would be a disaster....
btrfs_super_* marco should be used.

Thanks, will fix it.

Quoted 9 lines
Quoted 9 lines
(quoted diff) +9 -0
+	if (memcmp(fs_info->fsid, sb->dev_item.fsid, BTRFS_UUID_SIZE) != 0) {
+		printk(KERN_ERR "BTRFS: dev_item UUID does not match fsid: %pU != %pU\n",
+				fs_info->fsid, sb->dev_item.fsid);
+		ret = -EINVAL;
+	}
+
+	/*
+	 * Hint to catch really bogus numbers, bitflips or so, more exact checks are
+	 * done later
Quoted 8 lines

Review

Quoted 4 lines
(quoted diff) +4 -0
+	 */
+	if (sb->num_devices > (1UL << 31))
+		printk(KERN_WARNING "BTRFS: suspicious number of devices: %llu\n",
+				sb->num_devices);

What about also check the devid with sb->num_devices too? Every valid devid should be less equal than sb->num_devices if I am right. Although iterate dev_item here may be overkilled...

This could be done of course, I've tried to keep the checks very small and using only directly accessible information. More is possible of course.

Quoted 16 lines
Quoted 16 lines
(quoted diff) +16 -0
+
+	if (sb->bytenr != BTRFS_SUPER_INFO_OFFSET) {
+		printk(KERN_ERR "BTRFS: super offset mismatch %llu != %u\n",
+				sb->bytenr, BTRFS_SUPER_INFO_OFFSET);
+		ret = -EINVAL;
+	}
+
+	/*
+	 * The generation is a global counter, we'll trust it more than the others
+	 * but it's still possible that it's the one that's wrong.
+	 */
+	if (sb->generation < sb->chunk_root_generation)
+		printk(KERN_WARNING
+			"BTRFS: suspicious: generation < chunk_root_generation: %llu < %llu\n",
+			sb->generation, sb->chunk_root_generation);
+	if (sb->generation < sb->cache_generation && sb->cache_generation != (u64)-1)
Quoted 8 lines

Review

Quoted 7 lines
(quoted diff) +5 -0
+		printk(KERN_WARNING
+			"BTRFS: suspicious: generation < cache_generation: %llu < %llu\n",
+			sb->generation, sb->cache_generation);
+
+	return ret;
  }

Still the endian problem.

Will fix, thanks.