summaryrefslogtreecommitdiff
path: root/tests/generic/574
blob: cb42baaa67aa851cb211e68782cbce7819f6b29a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#! /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