diff mbox series

[v8,3/5] btrfs: test btrfs specific fsverity corruption

Message ID fd69c62b9971d446f53ba3d168625fb3a3468882.1647461985.git.boris@bur.io (mailing list archive)
State Superseded
Headers show
Series tests for btrfs fsverity | expand

Commit Message

Boris Burkov March 16, 2022, 8:25 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.

This test relies on the btrfs implementation of fsverity in the patch:
btrfs: initial fsverity support

and on btrfs-corrupt-block for corruption in the patches titled:
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>
---
 tests/btrfs/290     | 168 ++++++++++++++++++++++++++++++++++++++++++++
 tests/btrfs/290.out |  25 +++++++
 2 files changed, 193 insertions(+)
 create mode 100755 tests/btrfs/290
 create mode 100644 tests/btrfs/290.out

Comments

Eryu Guan April 10, 2022, 2:53 p.m. UTC | #1
On Wed, Mar 16, 2022 at 01:25:13PM -0700, 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.
> 
> This test relies on the btrfs implementation of fsverity in the patch:
> btrfs: initial fsverity support
> 
> and on btrfs-corrupt-block for corruption in the patches titled:
> 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>
> ---
>  tests/btrfs/290     | 168 ++++++++++++++++++++++++++++++++++++++++++++
>  tests/btrfs/290.out |  25 +++++++
>  2 files changed, 193 insertions(+)
>  create mode 100755 tests/btrfs/290
>  create mode 100644 tests/btrfs/290.out
> 
> diff --git a/tests/btrfs/290 b/tests/btrfs/290
> new file mode 100755
> index 00000000..f9acd55a
> --- /dev/null
> +++ b/tests/btrfs/290
> @@ -0,0 +1,168 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2021 Facebook, Inc. All Rights Reserved.

I noticed that all patches have 2021 in copyright statement, should be
updated to 2022?

And I'd like btrfs folks to help review these 2 btrfs specific tests.

Thanks,
Eryu

> +#
> +# FS QA Test 290
> +#
> +# Test btrfs support for fsverity.
> +# This test extends the generic fsverity testing by corrupting inline extents,
> +# preallocated extents, holes, and the Merkle descriptor in a btrfs-aware way.
> +#
> +. ./common/preamble
> +_begin_fstest auto quick verity
> +
> +# Import common functions.
> +. ./common/filter
> +. ./common/verity
> +
> +# real QA test starts here
> +_supported_fs btrfs
> +_require_scratch_verity
> +_require_scratch_nocheck
> +_require_odirect
> +_require_xfs_io_command "falloc"
> +_require_xfs_io_command "pread"
> +_require_xfs_io_command "pwrite"
> +_require_btrfs_corrupt_block
> +
> +get_ino() {
> +	local file=$1
> +	stat -c "%i" $file
> +}
> +
> +validate() {
> +	local f=$1
> +	local sz=$(_get_filesize $f)
> +	# buffered io
> +	echo $(basename $f)
> +	$XFS_IO_PROG -rc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
> +	# direct io
> +	$XFS_IO_PROG -rdc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
> +}
> +
> +# corrupt the data portion of an inline extent
> +corrupt_inline() {
> +	local f=$SCRATCH_MNT/inl
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 42" $f
> +	local ino=$(get_ino $f)
> +	_fsv_enable $f
> +	_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 2>&1
> +	_scratch_mount
> +	validate $f
> +}
> +
> +# preallocate a file, then corrupt it by changing it to a regular file
> +corrupt_prealloc_to_reg() {
> +	local f=$SCRATCH_MNT/prealloc
> +	$XFS_IO_PROG -fc "falloc 0 12k" $f
> +	local ino=$(get_ino $f)
> +	_fsv_enable $f
> +	_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 >/dev/null 2>&1
> +	_scratch_mount
> +	validate $f
> +}
> +
> +# corrupt a regular file by changing the type to preallocated
> +corrupt_reg_to_prealloc() {
> +	local f=$SCRATCH_MNT/reg
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> +	local ino=$(get_ino $f)
> +	_fsv_enable $f
> +	_scratch_unmount
> +	# set type from reg (1) to prealloc (2)
> +	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f type -v 2 $SCRATCH_DEV >/dev/null 2>&1
> +	_scratch_mount
> +	validate $f
> +}
> +
> +# corrupt a file by punching a hole
> +corrupt_punch_hole() {
> +	local f=$SCRATCH_MNT/punch
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> +	local ino=$(get_ino $f)
> +	# make a new extent in the middle, sync so the writes don't coalesce
> +	$XFS_IO_PROG -c sync $SCRATCH_MNT
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x59 4096 4096" $f
> +	_fsv_enable $f
> +	_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 2>&1
> +	_scratch_mount
> +	validate $f
> +}
> +
> +# plug hole
> +corrupt_plug_hole() {
> +	local f=$SCRATCH_MNT/plug
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> +	local ino=$(get_ino $f)
> +	$XFS_IO_PROG -fc "falloc 4k 4k" $f
> +	_fsv_enable $f
> +	_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 2>&1
> +	_scratch_mount
> +	validate $f
> +}
> +
> +# corrupt the fsverity descriptor item indiscriminately (causes EINVAL)
> +corrupt_verity_descriptor() {
> +	local f=$SCRATCH_MNT/desc
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> +	local ino=$(get_ino $f)
> +	_fsv_enable $f
> +	_scratch_unmount
> +	# key for the descriptor item is <inode, BTRFS_VERITY_DESC_ITEM_KEY, 1>,
> +	# 88 is X. So we write 5 Xs to the start of the descriptor
> +	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 0 -b 5 $SCRATCH_DEV > /dev/null 2>&1
> +	_scratch_mount
> +	validate $f
> +}
> +
> +# specifically target the root hash in the descriptor (causes EIO)
> +corrupt_root_hash() {
> +	local f=$SCRATCH_MNT/roothash
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> +	local ino=$(get_ino $f)
> +	_fsv_enable $f
> +	_scratch_unmount
> +	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 16 -b 1 $SCRATCH_DEV > /dev/null 2>&1
> +	_scratch_mount
> +	validate $f
> +}
> +
> +# corrupt the Merkle tree data itself
> +corrupt_merkle_tree() {
> +	local f=$SCRATCH_MNT/merkle
> +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> +	local ino=$(get_ino $f)
> +	_fsv_enable $f
> +	_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 2>&1
> +	_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
> +
> +status=0
> +exit
> diff --git a/tests/btrfs/290.out b/tests/btrfs/290.out
> new file mode 100644
> index 00000000..056b114b
> --- /dev/null
> +++ b/tests/btrfs/290.out
> @@ -0,0 +1,25 @@
> +QA output created by 290
> +inl
> +pread: Input/output error
> +pread: Input/output error
> +prealloc
> +pread: Input/output error
> +pread: Input/output error
> +reg
> +pread: Input/output error
> +pread: Input/output error
> +punch
> +pread: Input/output error
> +pread: Input/output error
> +plug
> +pread: Input/output error
> +pread: Input/output error
> +desc
> +SCRATCH_MNT/desc: Invalid argument
> +SCRATCH_MNT/desc: Invalid argument
> +roothash
> +pread: Input/output error
> +pread: Input/output error
> +merkle
> +pread: Input/output error
> +pread: Input/output error
> -- 
> 2.31.0
David Sterba April 11, 2022, 2:10 p.m. UTC | #2
On Sun, Apr 10, 2022 at 10:53:04PM +0800, Eryu Guan wrote:
> On Wed, Mar 16, 2022 at 01:25:13PM -0700, 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.
> > 
> > This test relies on the btrfs implementation of fsverity in the patch:
> > btrfs: initial fsverity support
> > 
> > and on btrfs-corrupt-block for corruption in the patches titled:
> > 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>
> > ---
> >  tests/btrfs/290     | 168 ++++++++++++++++++++++++++++++++++++++++++++
> >  tests/btrfs/290.out |  25 +++++++
> >  2 files changed, 193 insertions(+)
> >  create mode 100755 tests/btrfs/290
> >  create mode 100644 tests/btrfs/290.out
> > 
> > diff --git a/tests/btrfs/290 b/tests/btrfs/290
> > new file mode 100755
> > index 00000000..f9acd55a
> > --- /dev/null
> > +++ b/tests/btrfs/290
> > @@ -0,0 +1,168 @@
> > +#! /bin/bash
> > +# SPDX-License-Identifier: GPL-2.0
> > +# Copyright (C) 2021 Facebook, Inc. All Rights Reserved.
> 
> I noticed that all patches have 2021 in copyright statement, should be
> updated to 2022?
> 
> And I'd like btrfs folks to help review these 2 btrfs specific tests.

I did not do a deep review so no rev-by tag, but overall it looks sane
to me, also we'd rather like to have some fsverity tests in the suite
and fix them later eventually than nothing.
Boris Burkov April 11, 2022, 9:22 p.m. UTC | #3
On Sun, Apr 10, 2022 at 10:53:04PM +0800, Eryu Guan wrote:
> On Wed, Mar 16, 2022 at 01:25:13PM -0700, 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.
> > 
> > This test relies on the btrfs implementation of fsverity in the patch:
> > btrfs: initial fsverity support
> > 
> > and on btrfs-corrupt-block for corruption in the patches titled:
> > 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>
> > ---
> >  tests/btrfs/290     | 168 ++++++++++++++++++++++++++++++++++++++++++++
> >  tests/btrfs/290.out |  25 +++++++
> >  2 files changed, 193 insertions(+)
> >  create mode 100755 tests/btrfs/290
> >  create mode 100644 tests/btrfs/290.out
> > 
> > diff --git a/tests/btrfs/290 b/tests/btrfs/290
> > new file mode 100755
> > index 00000000..f9acd55a
> > --- /dev/null
> > +++ b/tests/btrfs/290
> > @@ -0,0 +1,168 @@
> > +#! /bin/bash
> > +# SPDX-License-Identifier: GPL-2.0
> > +# Copyright (C) 2021 Facebook, Inc. All Rights Reserved.
> 
> I noticed that all patches have 2021 in copyright statement, should be
> updated to 2022?

Oops, good catch. I will resend with the right copyright statement along
with any changes from review on the btrfs tests.

Thanks for taking a look,
Boris

> 
> And I'd like btrfs folks to help review these 2 btrfs specific tests.
> 
> Thanks,
> Eryu
> 
> > +#
> > +# FS QA Test 290
> > +#
> > +# Test btrfs support for fsverity.
> > +# This test extends the generic fsverity testing by corrupting inline extents,
> > +# preallocated extents, holes, and the Merkle descriptor in a btrfs-aware way.
> > +#
> > +. ./common/preamble
> > +_begin_fstest auto quick verity
> > +
> > +# Import common functions.
> > +. ./common/filter
> > +. ./common/verity
> > +
> > +# real QA test starts here
> > +_supported_fs btrfs
> > +_require_scratch_verity
> > +_require_scratch_nocheck
> > +_require_odirect
> > +_require_xfs_io_command "falloc"
> > +_require_xfs_io_command "pread"
> > +_require_xfs_io_command "pwrite"
> > +_require_btrfs_corrupt_block
> > +
> > +get_ino() {
> > +	local file=$1
> > +	stat -c "%i" $file
> > +}
> > +
> > +validate() {
> > +	local f=$1
> > +	local sz=$(_get_filesize $f)
> > +	# buffered io
> > +	echo $(basename $f)
> > +	$XFS_IO_PROG -rc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
> > +	# direct io
> > +	$XFS_IO_PROG -rdc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
> > +}
> > +
> > +# corrupt the data portion of an inline extent
> > +corrupt_inline() {
> > +	local f=$SCRATCH_MNT/inl
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 42" $f
> > +	local ino=$(get_ino $f)
> > +	_fsv_enable $f
> > +	_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 2>&1
> > +	_scratch_mount
> > +	validate $f
> > +}
> > +
> > +# preallocate a file, then corrupt it by changing it to a regular file
> > +corrupt_prealloc_to_reg() {
> > +	local f=$SCRATCH_MNT/prealloc
> > +	$XFS_IO_PROG -fc "falloc 0 12k" $f
> > +	local ino=$(get_ino $f)
> > +	_fsv_enable $f
> > +	_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 >/dev/null 2>&1
> > +	_scratch_mount
> > +	validate $f
> > +}
> > +
> > +# corrupt a regular file by changing the type to preallocated
> > +corrupt_reg_to_prealloc() {
> > +	local f=$SCRATCH_MNT/reg
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> > +	local ino=$(get_ino $f)
> > +	_fsv_enable $f
> > +	_scratch_unmount
> > +	# set type from reg (1) to prealloc (2)
> > +	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f type -v 2 $SCRATCH_DEV >/dev/null 2>&1
> > +	_scratch_mount
> > +	validate $f
> > +}
> > +
> > +# corrupt a file by punching a hole
> > +corrupt_punch_hole() {
> > +	local f=$SCRATCH_MNT/punch
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> > +	local ino=$(get_ino $f)
> > +	# make a new extent in the middle, sync so the writes don't coalesce
> > +	$XFS_IO_PROG -c sync $SCRATCH_MNT
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x59 4096 4096" $f
> > +	_fsv_enable $f
> > +	_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 2>&1
> > +	_scratch_mount
> > +	validate $f
> > +}
> > +
> > +# plug hole
> > +corrupt_plug_hole() {
> > +	local f=$SCRATCH_MNT/plug
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> > +	local ino=$(get_ino $f)
> > +	$XFS_IO_PROG -fc "falloc 4k 4k" $f
> > +	_fsv_enable $f
> > +	_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 2>&1
> > +	_scratch_mount
> > +	validate $f
> > +}
> > +
> > +# corrupt the fsverity descriptor item indiscriminately (causes EINVAL)
> > +corrupt_verity_descriptor() {
> > +	local f=$SCRATCH_MNT/desc
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> > +	local ino=$(get_ino $f)
> > +	_fsv_enable $f
> > +	_scratch_unmount
> > +	# key for the descriptor item is <inode, BTRFS_VERITY_DESC_ITEM_KEY, 1>,
> > +	# 88 is X. So we write 5 Xs to the start of the descriptor
> > +	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 0 -b 5 $SCRATCH_DEV > /dev/null 2>&1
> > +	_scratch_mount
> > +	validate $f
> > +}
> > +
> > +# specifically target the root hash in the descriptor (causes EIO)
> > +corrupt_root_hash() {
> > +	local f=$SCRATCH_MNT/roothash
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> > +	local ino=$(get_ino $f)
> > +	_fsv_enable $f
> > +	_scratch_unmount
> > +	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 16 -b 1 $SCRATCH_DEV > /dev/null 2>&1
> > +	_scratch_mount
> > +	validate $f
> > +}
> > +
> > +# corrupt the Merkle tree data itself
> > +corrupt_merkle_tree() {
> > +	local f=$SCRATCH_MNT/merkle
> > +	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
> > +	local ino=$(get_ino $f)
> > +	_fsv_enable $f
> > +	_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 2>&1
> > +	_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
> > +
> > +status=0
> > +exit
> > diff --git a/tests/btrfs/290.out b/tests/btrfs/290.out
> > new file mode 100644
> > index 00000000..056b114b
> > --- /dev/null
> > +++ b/tests/btrfs/290.out
> > @@ -0,0 +1,25 @@
> > +QA output created by 290
> > +inl
> > +pread: Input/output error
> > +pread: Input/output error
> > +prealloc
> > +pread: Input/output error
> > +pread: Input/output error
> > +reg
> > +pread: Input/output error
> > +pread: Input/output error
> > +punch
> > +pread: Input/output error
> > +pread: Input/output error
> > +plug
> > +pread: Input/output error
> > +pread: Input/output error
> > +desc
> > +SCRATCH_MNT/desc: Invalid argument
> > +SCRATCH_MNT/desc: Invalid argument
> > +roothash
> > +pread: Input/output error
> > +pread: Input/output error
> > +merkle
> > +pread: Input/output error
> > +pread: Input/output error
> > -- 
> > 2.31.0
diff mbox series

Patch

diff --git a/tests/btrfs/290 b/tests/btrfs/290
new file mode 100755
index 00000000..f9acd55a
--- /dev/null
+++ b/tests/btrfs/290
@@ -0,0 +1,168 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 Facebook, Inc. All Rights Reserved.
+#
+# FS QA Test 290
+#
+# Test btrfs support for fsverity.
+# This test extends the generic fsverity testing by corrupting inline extents,
+# preallocated extents, holes, and the Merkle descriptor in a btrfs-aware way.
+#
+. ./common/preamble
+_begin_fstest auto quick verity
+
+# Import common functions.
+. ./common/filter
+. ./common/verity
+
+# real QA test starts here
+_supported_fs btrfs
+_require_scratch_verity
+_require_scratch_nocheck
+_require_odirect
+_require_xfs_io_command "falloc"
+_require_xfs_io_command "pread"
+_require_xfs_io_command "pwrite"
+_require_btrfs_corrupt_block
+
+get_ino() {
+	local file=$1
+	stat -c "%i" $file
+}
+
+validate() {
+	local f=$1
+	local sz=$(_get_filesize $f)
+	# buffered io
+	echo $(basename $f)
+	$XFS_IO_PROG -rc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
+	# direct io
+	$XFS_IO_PROG -rdc "pread -q 0 $sz" $f 2>&1 | _filter_scratch
+}
+
+# corrupt the data portion of an inline extent
+corrupt_inline() {
+	local f=$SCRATCH_MNT/inl
+	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 42" $f
+	local ino=$(get_ino $f)
+	_fsv_enable $f
+	_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 2>&1
+	_scratch_mount
+	validate $f
+}
+
+# preallocate a file, then corrupt it by changing it to a regular file
+corrupt_prealloc_to_reg() {
+	local f=$SCRATCH_MNT/prealloc
+	$XFS_IO_PROG -fc "falloc 0 12k" $f
+	local ino=$(get_ino $f)
+	_fsv_enable $f
+	_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 >/dev/null 2>&1
+	_scratch_mount
+	validate $f
+}
+
+# corrupt a regular file by changing the type to preallocated
+corrupt_reg_to_prealloc() {
+	local f=$SCRATCH_MNT/reg
+	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
+	local ino=$(get_ino $f)
+	_fsv_enable $f
+	_scratch_unmount
+	# set type from reg (1) to prealloc (2)
+	$BTRFS_CORRUPT_BLOCK_PROG -i $ino -x 0 -f type -v 2 $SCRATCH_DEV >/dev/null 2>&1
+	_scratch_mount
+	validate $f
+}
+
+# corrupt a file by punching a hole
+corrupt_punch_hole() {
+	local f=$SCRATCH_MNT/punch
+	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
+	local ino=$(get_ino $f)
+	# make a new extent in the middle, sync so the writes don't coalesce
+	$XFS_IO_PROG -c sync $SCRATCH_MNT
+	$XFS_IO_PROG -fc "pwrite -q -S 0x59 4096 4096" $f
+	_fsv_enable $f
+	_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 2>&1
+	_scratch_mount
+	validate $f
+}
+
+# plug hole
+corrupt_plug_hole() {
+	local f=$SCRATCH_MNT/plug
+	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
+	local ino=$(get_ino $f)
+	$XFS_IO_PROG -fc "falloc 4k 4k" $f
+	_fsv_enable $f
+	_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 2>&1
+	_scratch_mount
+	validate $f
+}
+
+# corrupt the fsverity descriptor item indiscriminately (causes EINVAL)
+corrupt_verity_descriptor() {
+	local f=$SCRATCH_MNT/desc
+	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
+	local ino=$(get_ino $f)
+	_fsv_enable $f
+	_scratch_unmount
+	# key for the descriptor item is <inode, BTRFS_VERITY_DESC_ITEM_KEY, 1>,
+	# 88 is X. So we write 5 Xs to the start of the descriptor
+	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 0 -b 5 $SCRATCH_DEV > /dev/null 2>&1
+	_scratch_mount
+	validate $f
+}
+
+# specifically target the root hash in the descriptor (causes EIO)
+corrupt_root_hash() {
+	local f=$SCRATCH_MNT/roothash
+	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
+	local ino=$(get_ino $f)
+	_fsv_enable $f
+	_scratch_unmount
+	$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,36,1 -v 88 -o 16 -b 1 $SCRATCH_DEV > /dev/null 2>&1
+	_scratch_mount
+	validate $f
+}
+
+# corrupt the Merkle tree data itself
+corrupt_merkle_tree() {
+	local f=$SCRATCH_MNT/merkle
+	$XFS_IO_PROG -fc "pwrite -q -S 0x58 0 12288" $f
+	local ino=$(get_ino $f)
+	_fsv_enable $f
+	_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 2>&1
+	_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
+
+status=0
+exit
diff --git a/tests/btrfs/290.out b/tests/btrfs/290.out
new file mode 100644
index 00000000..056b114b
--- /dev/null
+++ b/tests/btrfs/290.out
@@ -0,0 +1,25 @@ 
+QA output created by 290
+inl
+pread: Input/output error
+pread: Input/output error
+prealloc
+pread: Input/output error
+pread: Input/output error
+reg
+pread: Input/output error
+pread: Input/output error
+punch
+pread: Input/output error
+pread: Input/output error
+plug
+pread: Input/output error
+pread: Input/output error
+desc
+SCRATCH_MNT/desc: Invalid argument
+SCRATCH_MNT/desc: Invalid argument
+roothash
+pread: Input/output error
+pread: Input/output error
+merkle
+pread: Input/output error
+pread: Input/output error