Skip to content

Commit 264241a

Browse files
adam900710gregkh
authored andcommitted
btrfs: check superblock to ensure the fs was not modified at thaw time
[ Upstream commit a05d3c9 ] [BACKGROUND] There is an incident report that, one user hibernated the system, with one btrfs on removable device still mounted. Then by some incident, the btrfs got mounted and modified by another system/OS, then back to the hibernated system. After resuming from the hibernation, new write happened into the victim btrfs. Now the fs is completely broken, since the underlying btrfs is no longer the same one before the hibernation, and the user lost their data due to various transid mismatch. [REPRODUCER] We can emulate the situation using the following small script: truncate -s 1G $dev mkfs.btrfs -f $dev mount $dev $mnt fsstress -w -d $mnt -n 500 sync xfs_freeze -f $mnt cp $dev $dev.backup # There is no way to mount the same cloned fs on the same system, # as the conflicting fsid will be rejected by btrfs. # Thus here we have to wipe the fs using a different btrfs. mkfs.btrfs -f $dev.backup dd if=$dev.backup of=$dev bs=1M xfs_freeze -u $mnt fsstress -w -d $mnt -n 20 umount $mnt btrfs check $dev The final fsck will fail due to some tree blocks has incorrect fsid. This is enough to emulate the problem hit by the unfortunate user. [ENHANCEMENT] Although such case should not be that common, it can still happen from time to time. From the view of btrfs, we can detect any unexpected super block change, and if there is any unexpected change, we just mark the fs read-only, and thaw the fs. By this we can limit the damage to minimal, and I hope no one would lose their data by this anymore. Suggested-by: Goffredo Baroncelli <kreijack@libero.it> Link: https://lore.kernel.org/linux-btrfs/83bf3b4b-7f4c-387a-b286-9251e3991e34@bluemole.com/ Reviewed-by: Anand Jain <anand.jain@oracle.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 69f4bda commit 264241a

File tree

4 files changed

+83
-8
lines changed

4 files changed

+83
-8
lines changed

fs/btrfs/disk-io.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2491,8 +2491,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info)
24912491
* 1, 2 2nd and 3rd backup copy
24922492
* -1 skip bytenr check
24932493
*/
2494-
static int validate_super(struct btrfs_fs_info *fs_info,
2495-
struct btrfs_super_block *sb, int mirror_num)
2494+
int btrfs_validate_super(struct btrfs_fs_info *fs_info,
2495+
struct btrfs_super_block *sb, int mirror_num)
24962496
{
24972497
u64 nodesize = btrfs_super_nodesize(sb);
24982498
u64 sectorsize = btrfs_super_sectorsize(sb);
@@ -2675,7 +2675,7 @@ static int validate_super(struct btrfs_fs_info *fs_info,
26752675
*/
26762676
static int btrfs_validate_mount_super(struct btrfs_fs_info *fs_info)
26772677
{
2678-
return validate_super(fs_info, fs_info->super_copy, 0);
2678+
return btrfs_validate_super(fs_info, fs_info->super_copy, 0);
26792679
}
26802680

26812681
/*
@@ -2689,7 +2689,7 @@ static int btrfs_validate_write_super(struct btrfs_fs_info *fs_info,
26892689
{
26902690
int ret;
26912691

2692-
ret = validate_super(fs_info, sb, -1);
2692+
ret = btrfs_validate_super(fs_info, sb, -1);
26932693
if (ret < 0)
26942694
goto out;
26952695
if (!btrfs_supported_super_csum(btrfs_super_csum_type(sb))) {
@@ -3703,7 +3703,7 @@ static void btrfs_end_super_write(struct bio *bio)
37033703
}
37043704

37053705
struct btrfs_super_block *btrfs_read_dev_one_super(struct block_device *bdev,
3706-
int copy_num)
3706+
int copy_num, bool drop_cache)
37073707
{
37083708
struct btrfs_super_block *super;
37093709
struct page *page;
@@ -3721,6 +3721,19 @@ struct btrfs_super_block *btrfs_read_dev_one_super(struct block_device *bdev,
37213721
if (bytenr + BTRFS_SUPER_INFO_SIZE >= i_size_read(bdev->bd_inode))
37223722
return ERR_PTR(-EINVAL);
37233723

3724+
if (drop_cache) {
3725+
/* This should only be called with the primary sb. */
3726+
ASSERT(copy_num == 0);
3727+
3728+
/*
3729+
* Drop the page of the primary superblock, so later read will
3730+
* always read from the device.
3731+
*/
3732+
invalidate_inode_pages2_range(mapping,
3733+
bytenr >> PAGE_SHIFT,
3734+
(bytenr + BTRFS_SUPER_INFO_SIZE) >> PAGE_SHIFT);
3735+
}
3736+
37243737
page = read_cache_page_gfp(mapping, bytenr >> PAGE_SHIFT, GFP_NOFS);
37253738
if (IS_ERR(page))
37263739
return ERR_CAST(page);
@@ -3752,7 +3765,7 @@ struct btrfs_super_block *btrfs_read_dev_super(struct block_device *bdev)
37523765
* later supers, using BTRFS_SUPER_MIRROR_MAX instead
37533766
*/
37543767
for (i = 0; i < 1; i++) {
3755-
super = btrfs_read_dev_one_super(bdev, i);
3768+
super = btrfs_read_dev_one_super(bdev, i, false);
37563769
if (IS_ERR(super))
37573770
continue;
37583771

fs/btrfs/disk-io.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ int __cold open_ctree(struct super_block *sb,
5656
struct btrfs_fs_devices *fs_devices,
5757
char *options);
5858
void __cold close_ctree(struct btrfs_fs_info *fs_info);
59+
int btrfs_validate_super(struct btrfs_fs_info *fs_info,
60+
struct btrfs_super_block *sb, int mirror_num);
5961
int write_all_supers(struct btrfs_fs_info *fs_info, int max_mirrors);
6062
struct btrfs_super_block *btrfs_read_dev_super(struct block_device *bdev);
6163
struct btrfs_super_block *btrfs_read_dev_one_super(struct block_device *bdev,
62-
int copy_num);
64+
int copy_num, bool drop_cache);
6365
int btrfs_commit_super(struct btrfs_fs_info *fs_info);
6466
struct btrfs_root *btrfs_read_tree_root(struct btrfs_root *tree_root,
6567
struct btrfs_key *key);

fs/btrfs/super.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2497,11 +2497,71 @@ static int btrfs_freeze(struct super_block *sb)
24972497
return btrfs_commit_transaction(trans);
24982498
}
24992499

2500+
static int check_dev_super(struct btrfs_device *dev)
2501+
{
2502+
struct btrfs_fs_info *fs_info = dev->fs_info;
2503+
struct btrfs_super_block *sb;
2504+
int ret = 0;
2505+
2506+
/* This should be called with fs still frozen. */
2507+
ASSERT(test_bit(BTRFS_FS_FROZEN, &fs_info->flags));
2508+
2509+
/* Missing dev, no need to check. */
2510+
if (!dev->bdev)
2511+
return 0;
2512+
2513+
/* Only need to check the primary super block. */
2514+
sb = btrfs_read_dev_one_super(dev->bdev, 0, true);
2515+
if (IS_ERR(sb))
2516+
return PTR_ERR(sb);
2517+
2518+
/* Btrfs_validate_super() includes fsid check against super->fsid. */
2519+
ret = btrfs_validate_super(fs_info, sb, 0);
2520+
if (ret < 0)
2521+
goto out;
2522+
2523+
if (btrfs_super_generation(sb) != fs_info->last_trans_committed) {
2524+
btrfs_err(fs_info, "transid mismatch, has %llu expect %llu",
2525+
btrfs_super_generation(sb),
2526+
fs_info->last_trans_committed);
2527+
ret = -EUCLEAN;
2528+
goto out;
2529+
}
2530+
out:
2531+
btrfs_release_disk_super(sb);
2532+
return ret;
2533+
}
2534+
25002535
static int btrfs_unfreeze(struct super_block *sb)
25012536
{
25022537
struct btrfs_fs_info *fs_info = btrfs_sb(sb);
2538+
struct btrfs_device *device;
2539+
int ret = 0;
25032540

2541+
/*
2542+
* Make sure the fs is not changed by accident (like hibernation then
2543+
* modified by other OS).
2544+
* If we found anything wrong, we mark the fs error immediately.
2545+
*
2546+
* And since the fs is frozen, no one can modify the fs yet, thus
2547+
* we don't need to hold device_list_mutex.
2548+
*/
2549+
list_for_each_entry(device, &fs_info->fs_devices->devices, dev_list) {
2550+
ret = check_dev_super(device);
2551+
if (ret < 0) {
2552+
btrfs_handle_fs_error(fs_info, ret,
2553+
"super block on devid %llu got modified unexpectedly",
2554+
device->devid);
2555+
break;
2556+
}
2557+
}
25042558
clear_bit(BTRFS_FS_FROZEN, &fs_info->flags);
2559+
2560+
/*
2561+
* We still return 0, to allow VFS layer to unfreeze the fs even the
2562+
* above checks failed. Since the fs is either fine or read-only, we're
2563+
* safe to continue, without causing further damage.
2564+
*/
25052565
return 0;
25062566
}
25072567

fs/btrfs/volumes.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2074,7 +2074,7 @@ void btrfs_scratch_superblocks(struct btrfs_fs_info *fs_info,
20742074
struct page *page;
20752075
int ret;
20762076

2077-
disk_super = btrfs_read_dev_one_super(bdev, copy_num);
2077+
disk_super = btrfs_read_dev_one_super(bdev, copy_num, false);
20782078
if (IS_ERR(disk_super))
20792079
continue;
20802080

0 commit comments

Comments
 (0)