diff mbox series

btrfs: add a test for btrfs fsverity

Message ID c16cbe8ad5795f059af45bfe7cf673dab58028a2.1612468162.git.boris@bur.io (mailing list archive)
State New, archived
Headers show
Series btrfs: add a test for btrfs fsverity | expand

Commit Message

Boris Burkov Feb. 4, 2021, 11:24 p.m. UTC
There are some btrfs specific fsverity scenarios that don't map
neatly onto the tests in generic/574, like holes, inline extents,
and preallocated extents. Cover those in a btrfs specific test.

That test relies on assumptions about how the Merkle tree is stored
by ext4/f2fs which don't apply to btrfs, so we also test Merkle tree
corruption here. This could be merged by some generic abstraction.

Finally, that test relies extensively on fiemap, which is currently
broken on btrfs for offsets and sizes that don't align to PAGE_SIZE,
so put a simple regular file case in this test for now, while we fix
fiemap or generalize extent lookup.

This test relies on the btrfs implementation of fsverity in:
btrfs: add compat_flags to btrfs_inode_item
btrfs: initial fsverity support
btrfs: check verity for reads of inline extents and holes
btrfs: fallback to buffered io for verity files
btrfs: add sysfs feature for fsverity

and it relies on btrfs-corrupt-block for corruption, with the patches:
btrfs-progs: corrupt generic item data with btrfs-corrupt-block
btrfs-progs: expand corrupt_file_extent in btrfs-corrupt-block

Signed-off-by: Boris Burkov <boris@bur.io>
---
 common/config       |   1 +
 common/verity       |   7 ++
 tests/btrfs/290     | 188 ++++++++++++++++++++++++++++++++++++++++++++
 tests/btrfs/290.out |  17 ++++
 tests/btrfs/group   |   1 +
 5 files changed, 214 insertions(+)
 create mode 100755 tests/btrfs/290
 create mode 100644 tests/btrfs/290.out

Comments

Eric Biggers Feb. 5, 2021, 6:04 a.m. UTC | #1
Thanks for writing a test for this!

On Thu, Feb 04, 2021 at 03:24:26PM -0800, Boris Burkov wrote:
> There are some btrfs specific fsverity scenarios that don't map
> neatly onto the tests in generic/574, like holes, inline extents,
> and preallocated extents. Cover those in a btrfs specific test.
> 
> That test relies on assumptions about how the Merkle tree is stored
> by ext4/f2fs which don't apply to btrfs, so we also test Merkle tree
> corruption here. This could be merged by some generic abstraction.

The only part of generic/574 that cares where the Merkle tree is stored is
_fsv_scratch_corrupt_merkle_tree().  Couldn't that be updated to handle btrfs?

> Finally, that test relies extensively on fiemap, which is currently
> broken on btrfs for offsets and sizes that don't align to PAGE_SIZE,
> so put a simple regular file case in this test for now, while we fix
> fiemap or generalize extent lookup.

fiemap is only used by _fsv_scratch_corrupt_bytes().  It just wants to know the
list of extents that intersect the requested byte range.  Does that really not
work on btrfs if the range isn't page-aligned?

- Eric
Boris Burkov Feb. 5, 2021, 6:38 a.m. UTC | #2
Thu, Feb 04, 2021 at 10:04:07PM -0800, Eric Biggers wrote:
> Thanks for writing a test for this!
> 
> On Thu, Feb 04, 2021 at 03:24:26PM -0800, Boris Burkov wrote:
> > There are some btrfs specific fsverity scenarios that don't map
> > neatly onto the tests in generic/574, like holes, inline extents,
> > and preallocated extents. Cover those in a btrfs specific test.
> > 
> > That test relies on assumptions about how the Merkle tree is stored
> > by ext4/f2fs which don't apply to btrfs, so we also test Merkle tree
> > corruption here. This could be merged by some generic abstraction.
> 
> The only part of generic/574 that cares where the Merkle tree is stored is
> _fsv_scratch_corrupt_merkle_tree().  Couldn't that be updated to handle btrfs?
> 

I agree that would be an easy enough fix, I'll make it in this patch.
Until I get 574 fully passing, I think I ought to leave the Merkle
corruption here as well, though, right?

> > Finally, that test relies extensively on fiemap, which is currently
> > broken on btrfs for offsets and sizes that don't align to PAGE_SIZE,
> > so put a simple regular file case in this test for now, while we fix
> > fiemap or generalize extent lookup.
> 
> fiemap is only used by _fsv_scratch_corrupt_bytes().  It just wants to know the
> list of extents that intersect the requested byte range.  Does that really not
> work on btrfs if the range isn't page-aligned?
> 

Unfortunately, fiemap is in fact broken in btrfs in that case, and prints
silly stuff like [K..K-1]. I wrote up a fix for it, but am still figuring
out how to test it, and decided to get the verity stuff out ahead of it.

However, even if I did get that fix in, it would still not be quite
right. Btrfs fiemap is in terms of logical block addresses, so
an additional translation with btrfs-map-logical is needed, and I
couldn't figure out how to elegantly inject that into
_fsv_scratch_corrupt_bytes. I do hope to get btrfs to pass generic/574
soon, though.

> - Eric 

Thanks for the review,
Boris
diff mbox series

Patch

diff --git a/common/config b/common/config
index d83dfb28..80d5ab66 100644
--- a/common/config
+++ b/common/config
@@ -254,6 +254,7 @@  export BTRFS_UTIL_PROG=$(type -P btrfs)
 export BTRFS_SHOW_SUPER_PROG=$(type -P btrfs-show-super)
 export BTRFS_CONVERT_PROG=$(type -P btrfs-convert)
 export BTRFS_TUNE_PROG=$(type -P btrfstune)
+export BTRFS_CORRUPT_BLOCK_PROG=$(type -P btrfs-corrupt-block)
 export XFS_FSR_PROG=$(type -P xfs_fsr)
 export MKFS_NFS_PROG="false"
 export MKFS_CIFS_PROG="false"
diff --git a/common/verity b/common/verity
index a8d3de06..c0b0c55d 100644
--- a/common/verity
+++ b/common/verity
@@ -8,6 +8,10 @@  _require_scratch_verity()
 	_require_scratch
 	_require_command "$FSVERITY_PROG" fsverity
 
+	if [ $FSTYP == "btrfs" ]; then
+		_require_command "$BTRFS_CORRUPT_BLOCK_PROG" btrfs_corrupt_block
+	fi
+
 	if ! _scratch_mkfs_verity &>>$seqres.full; then
 		# ext4: need e2fsprogs v1.44.5 or later (but actually v1.45.2+
 		#       is needed for some tests to pass, due to an e2fsck bug)
@@ -91,6 +95,9 @@  _scratch_mkfs_verity()
 	ext4|f2fs)
 		_scratch_mkfs -O verity
 		;;
+	btrfs)
+		_scratch_mkfs
+		;;
 	*)
 		_notrun "No verity support for $FSTYP"
 		;;
diff --git a/tests/btrfs/290 b/tests/btrfs/290
new file mode 100755
index 00000000..2e22e95a
--- /dev/null
+++ b/tests/btrfs/290
@@ -0,0 +1,188 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020 SUSE Linux Products GmbH. All Rights Reserved.
+#
+# FS QA Test 290
+#
+# Test btrfs support for fsverity
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/verity
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+_supported_fs btrfs
+_require_scratch
+_require_scratch_verity
+
+cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+_ino() {
+	file=$1
+	ls -i $file | awk '{print $1}'
+}
+
+_validate() {
+	f=$1
+	sz=$2
+	# buffered io
+	cat $f > /dev/null
+	# direct io
+	dd if=$f iflag=direct of=/dev/null status=none
+}
+
+# corrupt the data portion of an inline extent
+corrupt_inline() {
+	f=$SCRATCH_MNT/inl
+	head -c 42 /dev/zero | tr '\0' X > $f
+	ino=$(_ino $f)
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	# inline data starts at disk_bytenr
+	# overwrite the first u64 with random bogus junk
+	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f disk_bytenr $SCRATCH_DEV > /dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# preallocate a file, then corrupt it by changing it to a regular file
+corrupt_prealloc_to_reg() {
+	f=$SCRATCH_MNT/prealloc
+	fallocate -l 4k $f
+	ino=$(_ino $f)
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	# set extent type from prealloc (2) to reg (1)
+	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f type -v 1 $SCRATCH_DEV 2>/dev/null >/dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# corrupt a regular file by changing the type to preallocated
+corrupt_reg_to_prealloc() {
+	f=$SCRATCH_MNT/reg
+	head -c 12k /dev/zero | tr '\0' X > $f
+	ino=$(_ino $f)
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	# set type from reg (1) to prealloc (2)
+	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f type -v 2 $SCRATCH_DEV 2>/dev/null >/dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# corrupt a file by punching a hole
+corrupt_punch_hole() {
+	f=$SCRATCH_MNT/punch
+	head -c 12k /dev/zero | tr '\0' X > $f
+	ino=$(_ino $f)
+	# make a new extent in the middle
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	head -c 4k /dev/zero | tr '\0' Y | dd of=$f bs=4k count=1 seek=1 conv=notrunc 2>/dev/null
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	# change disk_bytenr to 0, representing a hole
+	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 4096 -f disk_bytenr -v 0 $SCRATCH_DEV > /dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# plug hole
+corrupt_plug_hole() {
+	f=$SCRATCH_MNT/plug
+	head -c 12k /dev/zero | tr '\0' X > $f
+	ino=$(_ino $f)
+	fallocate -p -o 4k -l 4k $f
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	# change disk_bytenr to some value, plugging the hole
+	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 4096 -f disk_bytenr -v 13639680 $SCRATCH_DEV > /dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# corrupt the fsverity descriptor item indiscriminately (causes EINVAL)
+corrupt_verity_descriptor() {
+	f=$SCRATCH_MNT/desc
+	head -c 12k /dev/zero | tr '\0' X > $f
+	ino=$(_ino $f)
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	# key for the descriptor item is <inode, BTRFS_VERITY_DESC_ITEM_KEY, 0>,
+	# 88 is X. So we write 5 Xs to the start of the descriptor
+	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,0 -v 88 -o 0 -b 5 $SCRATCH_DEV > /dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# specifically target the root hash in the descriptor (causes EIO)
+corrupt_root_hash() {
+	f=$SCRATCH_MNT/roothash
+	head -c 12k /dev/zero | tr '\0' X > $f
+	ino=$(_ino $f)
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,0 -v 88 -o 16 -b 1 $SCRATCH_DEV >> $seqres.full
+	#$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,0 -v 88 -o 120 -b 5 $SCRATCH_DEV > /dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# corrupt the Merkle tree data itself
+corrupt_merkle_tree() {
+	f=$SCRATCH_MNT/merkle
+	head -c 12k /dev/zero | tr '\0' X > $f
+	ino=$(_ino $f)
+	_fsv_enable $f
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	_scratch_unmount
+	# key for the descriptor item is <inode, BTRFS_VERITY_MERKLE_ITEM_KEY, 0>,
+	# 88 is X. So we write 5 Xs to somewhere in the middle of the first
+	# merkle item
+	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,37,0 -v 88 -o 100 -b 5 $SCRATCH_DEV > /dev/null
+	_scratch_mount
+	_validate $f
+}
+
+# real QA test starts here
+_scratch_mkfs >/dev/null
+_scratch_mount
+
+corrupt_inline
+corrupt_prealloc_to_reg
+corrupt_reg_to_prealloc
+corrupt_punch_hole
+corrupt_plug_hole
+corrupt_verity_descriptor
+corrupt_root_hash
+corrupt_merkle_tree
+
+# we intentionally corrupted, re-mkfs to avoid tripping the corrupted fs error
+_scratch_unmount
+_scratch_mkfs >/dev/null
+
+status=0
+exit
diff --git a/tests/btrfs/290.out b/tests/btrfs/290.out
new file mode 100644
index 00000000..4da61246
--- /dev/null
+++ b/tests/btrfs/290.out
@@ -0,0 +1,17 @@ 
+QA output created by 290
+cat: /mnt/scratch/inl: Input/output error
+dd: error reading '/mnt/scratch/inl': Input/output error
+cat: /mnt/scratch/prealloc: Input/output error
+dd: error reading '/mnt/scratch/prealloc': Input/output error
+cat: /mnt/scratch/reg: Input/output error
+dd: error reading '/mnt/scratch/reg': Input/output error
+cat: /mnt/scratch/punch: Input/output error
+dd: error reading '/mnt/scratch/punch': Input/output error
+cat: /mnt/scratch/plug: Input/output error
+dd: error reading '/mnt/scratch/plug': Input/output error
+cat: /mnt/scratch/desc: Invalid argument
+dd: failed to open '/mnt/scratch/desc': Invalid argument
+cat: /mnt/scratch/roothash: Input/output error
+dd: error reading '/mnt/scratch/roothash': Input/output error
+cat: /mnt/scratch/merkle: Input/output error
+dd: error reading '/mnt/scratch/merkle': Input/output error
diff --git a/tests/btrfs/group b/tests/btrfs/group
index a7c65983..58943c85 100644
--- a/tests/btrfs/group
+++ b/tests/btrfs/group
@@ -233,3 +233,4 @@ 
 228 auto quick volume
 229 auto quick send clone
 230 auto quick qgroup limit
+290 auto quick verity