common/verity: add common functions for testing fs-verity
[xfstests-dev.git] / common / verity
1 # SPDX-License-Identifier: GPL-2.0
2 # Copyright 2018 Google LLC
3 #
4 # Functions for setting up and testing fs-verity
5
6 _require_scratch_verity()
7 {
8         _require_scratch
9         _require_command "$FSVERITY_PROG" fsverity
10
11         if ! _scratch_mkfs_verity &>>$seqres.full; then
12                 # ext4: need e2fsprogs v1.44.5 or later (but actually v1.45.2+
13                 #       is needed for some tests to pass, due to an e2fsck bug)
14                 # f2fs: need f2fs-tools v1.11.0 or later
15                 _notrun "$FSTYP userspace tools don't support fs-verity"
16         fi
17
18         # Try to mount the filesystem.  If this fails then either the kernel
19         # isn't aware of fs-verity, or the mkfs options were not compatible with
20         # verity (e.g. ext4 with block size != PAGE_SIZE).
21         if ! _try_scratch_mount &>>$seqres.full; then
22                 _notrun "kernel is unaware of $FSTYP verity feature," \
23                         "or mkfs options are not compatible with verity"
24         fi
25
26         # The filesystem may be aware of fs-verity but have it disabled by
27         # CONFIG_FS_VERITY=n.  Detect support via sysfs.
28         if [ ! -e /sys/fs/$FSTYP/features/verity ]; then
29                 _notrun "kernel $FSTYP isn't configured with verity support"
30         fi
31
32         # The filesystem may have fs-verity enabled but not actually usable by
33         # default.  E.g., ext4 only supports verity on extent-based files, so it
34         # doesn't work on ext3-style filesystems.  So, try actually using it.
35         echo foo > $SCRATCH_MNT/tmpfile
36         if ! _fsv_enable $SCRATCH_MNT/tmpfile; then
37                 _notrun "$FSTYP verity isn't usable by default with these mkfs options"
38         fi
39         rm -f $SCRATCH_MNT/tmpfile
40
41         _scratch_unmount
42
43         # Merkle tree block size.  Currently all filesystems only support
44         # PAGE_SIZE for this.  This is also the default for 'fsverity enable'.
45         FSV_BLOCK_SIZE=$(get_page_size)
46 }
47
48 _scratch_mkfs_verity()
49 {
50         case $FSTYP in
51         ext4|f2fs)
52                 _scratch_mkfs -O verity
53                 ;;
54         *)
55                 _notrun "No verity support for $FSTYP"
56                 ;;
57         esac
58 }
59
60 _scratch_mkfs_encrypted_verity()
61 {
62         case $FSTYP in
63         ext4)
64                 _scratch_mkfs -O encrypt,verity
65                 ;;
66         f2fs)
67                 # f2fs-tools as of v1.11.0 doesn't allow comma-separated
68                 # features with -O.  Instead -O must be supplied multiple times.
69                 _scratch_mkfs -O encrypt -O verity
70                 ;;
71         *)
72                 _notrun "$FSTYP not supported in _scratch_mkfs_encrypted_verity"
73                 ;;
74         esac
75 }
76
77 _fsv_scratch_begin_subtest()
78 {
79         local msg=$1
80
81         rm -rf "${SCRATCH_MNT:?}"/*
82         echo -e "\n# $msg"
83 }
84
85 _fsv_enable()
86 {
87         $FSVERITY_PROG enable "$@"
88 }
89
90 _fsv_measure()
91 {
92         $FSVERITY_PROG measure "$@" | awk '{print $1}'
93 }
94
95 # Generate a file, then enable verity on it.
96 _fsv_create_enable_file()
97 {
98         local file=$1
99         shift
100
101         head -c $((FSV_BLOCK_SIZE * 2)) /dev/zero > "$file"
102         _fsv_enable "$file" "$@"
103 }
104
105 _fsv_have_hash_algorithm()
106 {
107         local hash_alg=$1
108         local test_file=$2
109
110         rm -f $test_file
111         head -c 4096 /dev/zero > $test_file
112         if ! _fsv_enable --hash-alg=$hash_alg $test_file &>> $seqres.full; then
113                 # no kernel support
114                 return 1
115         fi
116         rm -f $test_file
117         return 0
118 }
119
120 #
121 # _fsv_scratch_corrupt_bytes - Write some bytes to a file, bypassing the filesystem
122 #
123 # Write the bytes sent on stdin to the given offset in the given file, but do so
124 # by writing directly to the extents on the block device, with the filesystem
125 # unmounted.  This can be used to corrupt a verity file for testing purposes,
126 # bypassing the restrictions imposed by the filesystem.
127 #
128 # The file is assumed to be located on $SCRATCH_DEV.
129 #
130 _fsv_scratch_corrupt_bytes()
131 {
132         local file=$1
133         local offset=$2
134         local lstart lend pstart pend
135         local dd_cmds=()
136         local cmd
137
138         sync    # Sync to avoid unwritten extents
139
140         cat > $tmp.bytes
141         local end=$(( offset + $(stat -c %s $tmp.bytes ) ))
142
143         # For each extent that intersects the requested range in order, add a
144         # command that writes the next part of the data to that extent.
145         while read -r lstart lend pstart pend; do
146                 lstart=$((lstart * 512))
147                 lend=$(((lend + 1) * 512))
148                 pstart=$((pstart * 512))
149                 pend=$(((pend + 1) * 512))
150
151                 if (( lend - lstart != pend - pstart )); then
152                         _fail "Logical and physical extent lengths differ for file '$file'"
153                 elif (( offset < lstart )); then
154                         _fail "Hole in file '$file' at byte $offset.  Next extent begins at byte $lstart"
155                 elif (( offset < lend )); then
156                         local len=$((lend - offset))
157                         local seek=$((pstart + (offset - lstart)))
158                         dd_cmds+=("head -c $len | dd of=$SCRATCH_DEV oflag=seek_bytes seek=$seek status=none")
159                         (( offset += len ))
160                 fi
161         done < <($XFS_IO_PROG -r -c "fiemap $offset $((end - offset))" "$file" \
162                  | _filter_xfs_io_fiemap)
163
164         if (( offset < end )); then
165                 _fail "Extents of file '$file' ended at byte $offset, but needed until $end"
166         fi
167
168         # Execute the commands to write the data
169         _scratch_unmount
170         for cmd in "${dd_cmds[@]}"; do
171                 eval "$cmd"
172         done < $tmp.bytes
173         sync    # Sync to flush the block device's pagecache
174         _scratch_mount
175 }
176
177 #
178 # _fsv_scratch_corrupt_merkle_tree - Corrupt a file's Merkle tree
179 #
180 # Like _fsv_scratch_corrupt_bytes(), but this corrupts the file's fs-verity
181 # Merkle tree.  The offset is given as a byte offset into the Merkle tree.
182 #
183 _fsv_scratch_corrupt_merkle_tree()
184 {
185         local file=$1
186         local offset=$2
187
188         case $FSTYP in
189         ext4|f2fs)
190                 # ext4 and f2fs store the Merkle tree after the file contents
191                 # itself, starting at the next 65536-byte aligned boundary.
192                 (( offset += ($(stat -c %s $file) + 65535) & ~65535 ))
193                 _fsv_scratch_corrupt_bytes $file $offset
194                 ;;
195         *)
196                 _fail "_fsv_scratch_corrupt_merkle_tree() unimplemented on $FSTYP"
197                 ;;
198         esac
199 }