diff --git a/common/btrfs b/common/btrfs
index 670d9d1f..c3a7dc6e 100644
--- a/common/btrfs
+++ b/common/btrfs
@@ -511,3 +511,8 @@ _btrfs_metadump()
 	$BTRFS_IMAGE_PROG "$device" "$dumpfile"
 	[ -n "$DUMP_COMPRESSOR" ] && $DUMP_COMPRESSOR -f "$dumpfile" &> /dev/null
 }
+
+_require_btrfs_corrupt_block()
+{
+	_require_command "$BTRFS_CORRUPT_BLOCK_PROG" btrfs-corrupt-block
+}
diff --git a/common/config b/common/config
index 479e50d1..67bdf912 100644
--- a/common/config
+++ b/common/config
@@ -296,6 +296,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 d58cad90..8cde2737 100644
--- a/common/verity
+++ b/common/verity
@@ -3,6 +3,13 @@
 #
 # Functions for setting up and testing fs-verity
 
+. common/btrfs
+# btrfs will return IO errors on corrupted data with or without fs-verity.
+# to really test fs-verity, use nodatasum.
+if [ "$FSTYP" == "btrfs" ]; then
+	export MOUNT_OPTIONS="-o nodatasum"
+fi
+
 _require_scratch_verity()
 {
 	_require_scratch
@@ -145,6 +152,9 @@ _require_fsverity_dump_metadata()
 _require_fsverity_corruption()
 {
 	_require_xfs_io_command "fiemap"
+	if [ $FSTYP == "btrfs" ]; then
+		_require_btrfs_corrupt_block
+	fi
 }
 
 _scratch_mkfs_verity()
@@ -153,6 +163,9 @@ _scratch_mkfs_verity()
 	ext4|f2fs)
 		_scratch_mkfs -O verity
 		;;
+	btrfs)
+		_scratch_mkfs
+		;;
 	*)
 		_notrun "No verity support for $FSTYP"
 		;;
@@ -314,6 +327,21 @@ _fsv_scratch_corrupt_merkle_tree()
 		(( offset += ($(_get_filesize $file) + 65535) & ~65535 ))
 		_fsv_scratch_corrupt_bytes $file $offset
 		;;
+	btrfs)
+		local ino=$(stat -c '%i' $file)
+		_scratch_unmount
+		local byte=""
+		while read -n 1 byte; do
+			local ascii=$(printf "%d" "'$byte'")
+			# This command will find a Merkle tree item for the inode (-I $ino,37,0)
+			# in the default filesystem tree (-r 5) and corrupt one byte (-b 1) at
+			# $offset (-o $offset) with the ascii representation of the byte we read
+			# (-v $ascii)
+			$BTRFS_CORRUPT_BLOCK_PROG -r 5 -I $ino,37,0 -v $ascii -o $offset -b 1 $SCRATCH_DEV
+			(( offset += 1 ))
+		done
+		_scratch_mount
+		;;
 	*)
 		_fail "_fsv_scratch_corrupt_merkle_tree() unimplemented on $FSTYP"
 		;;
diff --git a/tests/generic/574 b/tests/generic/574
index 17fdea52..680cece3 100755
--- a/tests/generic/574
+++ b/tests/generic/574
@@ -126,6 +126,41 @@ corruption_test()
 	fi
 }
 
+# xfs_io mread's output is parseable by xxd -r, except it has an extra space
+# after the colon. Output the number of non zero characters in the parsed contents.
+filter_mread() {
+	sed 's/:  /: /' | xxd -r | sed 's/\x0//g' | wc -c
+}
+
+# this expects to see stdout + stderr passed through filter_sigbus and filter_mread.
+# Outputs "OK" on a bus error or 0 non-zero characters counted by mread.
+filter_eof_block() {
+	sed 's/^Bus error$/OK/' | sed 's/^0$/OK/'
+}
+
+# some filesystems return zeros in the last block past EOF, regardless of
+# their contents. Handle those with a special test that accepts either zeros
+# or SIGBUS on an mmap+read of that block.
+corrupt_eof_block_test() {
+	local file_len=$1
+	local zap_len=$2
+	local page_aligned_eof=$(round_up_to_page_boundary $file_len)
+	local eof_page_start=$((page_aligned_eof - $(get_page_size)))
+	local corrupt_func=_fsv_scratch_corrupt_bytes
+	_fsv_scratch_begin_subtest "Corruption test: EOF block"
+	setup_zeroed_file $file_len false
+	cmp $fsv_file $fsv_orig_file
+	echo "Corrupting bytes..."
+	head -c $zap_len /dev/zero | tr '\0' X \
+		| $corrupt_func $fsv_file $((file_len + 1))
+
+	echo "Validating corruption or zeros (reading eof block via mmap)..."
+	bash -c "trap '' SIGBUS; $XFS_IO_PROG -r $fsv_file \
+		-c 'mmap -r $eof_page_start $(get_page_size)' \
+		-c 'mread -v $eof_page_start $(get_page_size)'" \
+		|& filter_mread | filter_sigbus | filter_eof_block
+}
+
 # Note: these tests just overwrite some bytes without checking their original
 # values.  Therefore, make sure to overwrite at least 5 or so bytes, to make it
 # nearly guaranteed that there will be a change -- even when the test file is
@@ -137,8 +172,8 @@ corruption_test 131072 65536 65536
 corruption_test 131072 131067 5
 
 # Non-zeroed bytes in the final partial block beyond EOF should cause reads to
-# fail too.  Such bytes would be visible via mmap().
-corruption_test 130999 131000 72
+# fail too.  Such bytes could be visible via mmap().
+corrupt_eof_block_test 130999 72
 
 # Merkle tree corruption.
 corruption_test 200000 100 10 true
diff --git a/tests/generic/574.out b/tests/generic/574.out
index 3c08d3e8..51bbd17b 100644
--- a/tests/generic/574.out
+++ b/tests/generic/574.out
@@ -56,17 +56,11 @@ Bus error
 Validating corruption (reading just corrupted part via mmap)...
 Bus error
 
-# Corruption test: file_len=130999 zap_offset=131000 zap_len=72
+# Corruption test: EOF block
 f5cca0d7fbb8b02bc6118a9954d5d306  SCRATCH_MNT/file.fsv
 Corrupting bytes...
-Validating corruption (reading full file)...
-md5sum: SCRATCH_MNT/file.fsv: Input/output error
-Validating corruption (direct I/O)...
-dd: error reading 'SCRATCH_MNT/file.fsv': Input/output error
-Validating corruption (reading full file via mmap)...
-Bus error
-Validating corruption (reading just corrupted part via mmap)...
-Bus error
+Validating corruption or zeros (reading eof block via mmap)...
+OK
 
 # Corruption test: file_len=200000 zap_offset=100 (in Merkle tree) zap_len=10
 4a1e4325031b13f933ac4f1db9ecb63f  SCRATCH_MNT/file.fsv
