#! /bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright 2018 Google LLC # # FS QA Test generic/574 # # Test corrupting verity files. This test corrupts various parts of the # contents of a verity file, or parts of its Merkle tree, by writing directly to # the block device. It verifies that this causes I/O errors when the relevant # part of the contents is later read by any means. # . ./common/preamble _begin_fstest auto quick verity # Override the default cleanup function. _cleanup() { cd / _restore_fsverity_signatures rm -f $tmp.* } # Import common functions. . ./common/filter . ./common/verity # real QA test starts here _supported_fs generic _require_scratch_verity _disable_fsverity_signatures _require_fsverity_corruption _scratch_mkfs_verity &>> $seqres.full _scratch_mount fsv_orig_file=$SCRATCH_MNT/file fsv_file=$SCRATCH_MNT/file.fsv setup_zeroed_file() { local block_size=$1 local file_len=$2 local sparse=$3 if $sparse; then dd if=/dev/zero of=$fsv_orig_file bs=1 count=0 seek=$file_len \ status=none else head -c $file_len /dev/zero > $fsv_orig_file fi cp $fsv_orig_file $fsv_file _fsv_enable $fsv_file --block-size=$block_size cmp $fsv_orig_file $fsv_file } round_up_to_page_boundary() { local n=$1 local page_size=$(_get_page_size) echo $(( (n + page_size - 1) & ~(page_size - 1) )) } mread() { local file=$1 local offset=$2 local length=$3 local map_len=$(round_up_to_page_boundary $(_get_filesize $file)) # Some callers expect xfs_io to crash with SIGBUS due to the mread, # causing the shell to print "Bus error" to stderr. To allow this # message to be redirected, execute xfs_io in a new shell instance. # However, for this to work reliably, we also need to prevent the new # shell instance from optimizing out the fork and directly exec'ing # xfs_io. The easiest way to do that is to append 'true' to the # commands, so that xfs_io is no longer the last command the shell sees. # Don't let it write core files to the filesystem. bash -c "trap '' SIGBUS; ulimit -c 0; $XFS_IO_PROG -r $file \ -c 'mmap -r 0 $map_len' \ -c 'mread -v $offset $length'; true" } corruption_test() { local block_size=$1 local file_len=$2 local zap_offset=$3 local zap_len=$4 local is_merkle_tree=${5:-false} # if true, zap tree instead of data local use_sparse_file=${6:-false} local paramstr="block_size=$block_size" paramstr+=", file_len=$file_len" paramstr+=", zap_offset=$zap_offset" paramstr+=", zap_len=$zap_len" paramstr+=", is_merkle_tree=$is_merkle_tree" paramstr+=", use_sparse_file=$use_sparse_file" if $is_merkle_tree; then local corrupt_func=_fsv_scratch_corrupt_merkle_tree else local corrupt_func=_fsv_scratch_corrupt_bytes fi local fs_block_size=$(_get_block_size $SCRATCH_MNT) rm -rf "${SCRATCH_MNT:?}"/* setup_zeroed_file $block_size $file_len $use_sparse_file # Corrupt part of the file (data or Merkle tree). head -c $zap_len /dev/zero | tr '\0' X \ | $corrupt_func $fsv_file $zap_offset # Reading the full file with buffered I/O should fail. _scratch_cycle_mount if cat $fsv_file >/dev/null 2>$tmp.err; then echo "Unexpectedly was able to read full file ($paramstr)" elif ! grep -q 'Input/output error' $tmp.err; then echo "Wrong error reading full file ($paramstr):" cat $tmp.err fi # Reading the full file with direct I/O should fail. _scratch_cycle_mount if dd if=$fsv_file bs=$fs_block_size iflag=direct status=none \ of=/dev/null 2>$tmp.err then echo "Unexpectedly was able to read full file with DIO ($paramstr)" elif ! grep -q 'Input/output error' $tmp.err; then echo "Wrong error reading full file with DIO ($paramstr):" cat $tmp.err fi # Reading just the corrupted part of the file should fail. if ! $is_merkle_tree; then if dd if=$fsv_file bs=1 skip=$zap_offset count=$zap_len \ of=/dev/null status=none 2>$tmp.err; then echo "Unexpectedly was able to read corrupted part ($paramstr)" elif ! grep -q 'Input/output error' $tmp.err; then echo "Wrong error reading corrupted part ($paramstr):" cat $tmp.err fi fi # Reading the full file via mmap should fail. mread $fsv_file 0 $file_len >/dev/null 2>$tmp.err if ! grep -q 'Bus error' $tmp.err; then echo "Didn't see SIGBUS when reading file via mmap" cat $tmp.err fi # Reading just the corrupted part via mmap should fail. if ! $is_merkle_tree; then mread $fsv_file $zap_offset $zap_len >/dev/null 2>$tmp.err if ! grep -q 'Bus error' $tmp.err; then echo "Didn't see SIGBUS when reading corrupted part via mmap" cat $tmp.err fi fi } # Reading the last block of the file with mmap is tricky, so we need to be # a bit careful. Some filesystems read the last block in full, while others # return zeros in the last block past EOF, regardless of the contents on # disk. In the former, corruption should be detected and result in SIGBUS, # while in the latter we would expect zeros past EOF, but no error. corrupt_eof_block_test() { local block_size=$1 local file_len=$2 local zap_len=$3 rm -rf "${SCRATCH_MNT:?}"/* setup_zeroed_file $block_size $file_len false head -c $zap_len /dev/zero | tr '\0' X \ | _fsv_scratch_corrupt_bytes $fsv_file $file_len mread $fsv_file $file_len $zap_len >$tmp.out 2>$tmp.err head -c $file_len /dev/zero >$tmp.zeroes mread $tmp.zeroes $file_len $zap_len >$tmp.zeroes_out grep -q 'Bus error' $tmp.err || diff $tmp.out $tmp.zeroes_out } test_block_size() { local block_size=$1 # 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 encrypted due to the test_dummy_encryption # mount option being specified. corruption_test $block_size 131072 0 5 corruption_test $block_size 131072 4091 5 corruption_test $block_size 131072 65536 65536 corruption_test $block_size 131072 131067 5 corrupt_eof_block_test $block_size 130999 72 # Merkle tree corruption. corruption_test $block_size 200000 100 10 true # Sparse file. Corrupting the Merkle tree should still cause reads to # fail, i.e. the filesystem must verify holes. corruption_test $block_size 200000 100 10 true true } # Always test FSV_BLOCK_SIZE. Also test some other block sizes if they happen # to be supported. _fsv_scratch_begin_subtest "Testing block_size=FSV_BLOCK_SIZE" test_block_size $FSV_BLOCK_SIZE for block_size in 1024 4096 16384 65536; do _fsv_scratch_begin_subtest "Testing block_size=$block_size if supported" if (( block_size == FSV_BLOCK_SIZE )); then continue # Skip redundant test case. fi if ! _fsv_can_enable $fsv_file --block-size=$block_size; then echo "block_size=$block_size is unsupported" >> $seqres.full continue fi test_block_size $block_size done # success, all done status=0 exit