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
|