diff mbox series

[v2] fstests: shared: generic: check if one fs can detect damage at fs thaw

Message ID 20221117091737.65822-1-wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series [v2] fstests: shared: generic: check if one fs can detect damage at fs thaw | expand

Commit Message

Qu Wenruo Nov. 17, 2022, 9:17 a.m. UTC
[BACKGROUND]
There is bug report from btrfs mailing list that, hibernation can allow
one to modify the frozen filesystem unexpectedly (using another OS).
(https://lore.kernel.org/linux-btrfs/83bf3b4b-7f4c-387a-b286-9251e3991e34@bluemole.com/)

Later btrfs adds the check to make sure the fs is not changed
unexpectedly, to prevent corruption from happening.

[TESTCASE]
The test case will test the following three cases:

- Completely corrupted super block
  Fill the superblock range (the first 1M) with garbage.

- Superblock is valid, but has different fsid
  We save a binary dump of a newly created fs, then create
  another fs on the scratch device.
  After the fs got frozen, write the saved binary dump back.

- Superblock is valid, but has different generation.
  We save a binary dump of a newly created fs.

  Then modify the created fs, then freeze it.
  After the fs got frozen, write the saved binary dump back.

And since we're using "$tmp.binary_dump" to save the whole 512M fs,
systems with small memory and using tmpfs as /tmp may fail to save the
image.
Thus before the run, the test case will do a dry run to make sure we can
save the image into "$tmp.binary_dump" first.

Currently only btrfs does such explicit check at thaw time, thus it will
mark the fs RO immediately.

Other common fses like XFS/EXT4 can detect the problem at read or write
time, but no explicit check at thaw time yet.
Feel free to opt-in the new test case if the early check sounds valid.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
Changelog:
v2:
- Move the test case to shared group
  This allow each fs to opt-in.

- Add two new types of super block modification
  Other than pure garbage, also introduce:
  * Valid superblock but different fsid
  * valid superblock and same fsid, but different generation

- Remove cache drop
  To make the explicit thaw time check more obvious.
---
 tests/shared/001     | 116 +++++++++++++++++++++++++++++++++++++++++++
 tests/shared/001.out |  10 ++++
 2 files changed, 126 insertions(+)
 create mode 100755 tests/shared/001
 create mode 100644 tests/shared/001.out
diff mbox series

Patch

diff --git a/tests/shared/001 b/tests/shared/001
new file mode 100755
index 00000000..a160cd06
--- /dev/null
+++ b/tests/shared/001
@@ -0,0 +1,116 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2022 SUSE Linux Products GmbH. All Rights Reserved.
+#
+# FS QA Test 001
+#
+# Test if a filesystem can detect unexpected changes at thaw time.
+#
+
+. ./common/preamble
+_begin_fstest auto freeze
+
+# For now, only btrfs explicitly checks the superblock at thaw time.
+_supported_fs btrfs
+_require_freeze
+_require_scratch
+
+if [ "x$FSTYP" == "xbtrfs" ]; then
+	_fixed_by_kernel_commit a05d3c915314 \
+		"btrfs: check superblock to ensure the fs was not modified at thaw time"
+fi
+
+# Make sure we can save the whole binary dump of the fs into $tmp, which
+# is normally backed up by tmpfs, thus may have very limited size.
+dd if=/dev/zero of=$tmp.binary_dump bs=1M count=512 >> $seqres.full 2>&1
+if [ $? -ne 0 ]; then
+	_notrun "Failed to save 512M sized image into $tmp.binary, maybe /tmp is too small?"
+fi
+rm -f $tmp.binary_dump
+
+prepare_and_freeze()
+{
+	_scratch_mount
+
+	$FSSTRESS_PROG -d $SCRATCH_MNT -n 100 -w >> $seqres.full
+	sync
+
+	$XFS_IO_PROG -x -c "freeze" $SCRATCH_MNT >> $seqres.full
+}
+
+thaw_and_verify()
+{
+	# We can not rely on the return value from thaw operation,
+	# as even some checks failed, we have to return 0 to allow
+	# the fs exit frezon status. Thus we do the check later.
+	$XFS_IO_PROG -x -c "thaw" $SCRATCH_MNT >> $seqres.full 2>&1
+
+	touch $SCRATCH_MNT/should_fail &> $seqres.full
+	if [ $? -eq 0 ]; then
+		echo "Failed to detect corrupted super block"
+	else
+		echo "Detected corrupted super block and fs fell RO"
+	fi
+	echo
+	_scratch_unmount
+}
+
+save_scratch_dev()
+{
+	dd if=$SCRATCH_DEV of=$tmp.binary_dump bs=1M count=512 >> $seqres.full 2>&1
+	if [ $? -ne 0 ]; then
+		_fail "Unable to save the full fs binary dump"
+	fi
+}
+
+restore_scratch_dev()
+{
+	dd if=$tmp.binary_dump of=$SCRATCH_DEV bs=1M >> $seqres.full 2>&1
+	if [ $? -ne 0 ]; then
+		_fail "Unable to restore the full fs binary dump"
+	fi
+	rm -f $tmp.binary_dump
+}
+
+test_corrupted_super()
+{
+	echo "Corrupted super block at thaw time:"
+	_scratch_mkfs_sized $((512 * 1024 * 1024)) >> $seqres.full
+	prepare_and_freeze
+	# Corrupt the first 1M to cover the superblock.
+	dd if=/dev/zero of=$SCRATCH_DEV bs=1M count=1 >> $seqres.full 2>&1
+	thaw_and_verify
+}
+
+test_different_fs()
+{
+	echo "Different fs at thaw time:"
+	_scratch_mkfs_sized $((512 * 1024 * 1024)) >> $seqres.full
+	save_scratch_dev
+
+	# Now go a new fs to start the test.
+	_scratch_mkfs_sized $((512 * 1024 * 1024)) >> $seqres.full
+
+	prepare_and_freeze
+	restore_scratch_dev
+	thaw_and_verify
+}
+
+test_different_generation()
+{
+	echo "Same fs but different generation:"
+
+	_scratch_mkfs_sized $((512 * 1024 * 1024)) >> $seqres.full
+	save_scratch_dev
+	prepare_and_freeze
+	restore_scratch_dev
+	thaw_and_verify
+}
+
+test_corrupted_super
+test_different_fs
+test_different_generation
+
+# success, all done
+status=0
+exit
diff --git a/tests/shared/001.out b/tests/shared/001.out
new file mode 100644
index 00000000..2aa0e02a
--- /dev/null
+++ b/tests/shared/001.out
@@ -0,0 +1,10 @@ 
+QA output created by 001
+Corrupted super block at thaw time:
+Detected corrupted super block and fs fell RO
+
+Different fs at thaw time:
+Detected corrupted super block and fs fell RO
+
+Same fs but different generation:
+Detected corrupted super block and fs fell RO
+