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 <bo...@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

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
-- 
2.30.0

Reply via email to