ext4: prevent initializing reserved inodes
[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         _disable_fsverity_signatures
37         if ! _fsv_enable $SCRATCH_MNT/tmpfile; then
38                 _restore_fsverity_signatures
39                 _notrun "$FSTYP verity isn't usable by default with these mkfs options"
40         fi
41         _restore_fsverity_signatures
42         rm -f $SCRATCH_MNT/tmpfile
43
44         _scratch_unmount
45
46         # Merkle tree block size.  Currently all filesystems only support
47         # PAGE_SIZE for this.  This is also the default for 'fsverity enable'.
48         FSV_BLOCK_SIZE=$(get_page_size)
49 }
50
51 # Check for CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y, as well as the userspace
52 # commands needed to generate certificates and add them to the kernel.
53 _require_fsverity_builtin_signatures()
54 {
55         if [ ! -e /proc/sys/fs/verity/require_signatures ]; then
56                 _notrun "kernel doesn't support fs-verity builtin signatures"
57         fi
58         _require_command "$OPENSSL_PROG" openssl
59         _require_command "$KEYCTL_PROG" keyctl
60 }
61
62 # Use the openssl program to generate a private key and a X.509 certificate for
63 # use with fs-verity built-in signature verification, and convert the
64 # certificate to DER format.
65 _fsv_generate_cert()
66 {
67         local keyfile=$1
68         local certfile=$2
69         local certfileder=$3
70
71         if ! $OPENSSL_PROG req -newkey rsa:4096 -nodes -batch -x509 \
72                         -keyout $keyfile -out $certfile &>> $seqres.full; then
73                 _fail "Failed to generate certificate and private key (see $seqres.full)"
74         fi
75         $OPENSSL_PROG x509 -in $certfile -out $certfileder -outform der
76 }
77
78 # Clear the .fs-verity keyring.
79 _fsv_clear_keyring()
80 {
81         $KEYCTL_PROG clear %keyring:.fs-verity
82 }
83
84 # Load the given X.509 certificate in DER format into the .fs-verity keyring so
85 # that the kernel can use it to verify built-in signatures.
86 _fsv_load_cert()
87 {
88         local certfileder=$1
89
90         $KEYCTL_PROG padd asymmetric '' %keyring:.fs-verity \
91                 < $certfileder >> $seqres.full
92 }
93
94 # Disable mandatory signatures for fs-verity files, if they are supported.
95 _disable_fsverity_signatures()
96 {
97         if [ -e /proc/sys/fs/verity/require_signatures ]; then
98                 if [ -z "$FSVERITY_SIG_CTL_ORIG" ]; then
99                         FSVERITY_SIG_CTL_ORIG=$(</proc/sys/fs/verity/require_signatures)
100                 fi
101                 echo 0 > /proc/sys/fs/verity/require_signatures
102         fi
103 }
104
105 # Enable mandatory signatures for fs-verity files.
106 # This assumes that _require_fsverity_builtin_signatures() was called.
107 _enable_fsverity_signatures()
108 {
109         if [ -z "$FSVERITY_SIG_CTL_ORIG" ]; then
110                 FSVERITY_SIG_CTL_ORIG=$(</proc/sys/fs/verity/require_signatures)
111         fi
112         echo 1 > /proc/sys/fs/verity/require_signatures
113 }
114
115 # Restore the original signature verification setting.
116 _restore_fsverity_signatures()
117 {
118         if [ -n "$FSVERITY_SIG_CTL_ORIG" ]; then
119                 echo "$FSVERITY_SIG_CTL_ORIG" > /proc/sys/fs/verity/require_signatures
120         fi
121 }
122
123 # Require userspace and kernel support for 'fsverity dump_metadata'.
124 # $1 must be a file with fs-verity enabled.
125 _require_fsverity_dump_metadata()
126 {
127         local verity_file=$1
128         local tmpfile=$tmp.require_fsverity_dump_metadata
129
130         if _fsv_dump_merkle_tree "$verity_file" 2>"$tmpfile" >/dev/null; then
131                 return
132         fi
133         if grep -q "^ERROR: unrecognized command: 'dump_metadata'$" "$tmpfile"
134         then
135                 _notrun "Missing 'fsverity dump_metadata' command"
136         fi
137         if grep -q "^ERROR: FS_IOC_READ_VERITY_METADATA failed on '.*': Inappropriate ioctl for device$" "$tmpfile"
138         then
139                 _notrun "Kernel doesn't support FS_IOC_READ_VERITY_METADATA"
140         fi
141         _fail "Unexpected output from 'fsverity dump_metadata': $(<"$tmpfile")"
142 }
143
144 _scratch_mkfs_verity()
145 {
146         case $FSTYP in
147         ext4|f2fs)
148                 _scratch_mkfs -O verity
149                 ;;
150         *)
151                 _notrun "No verity support for $FSTYP"
152                 ;;
153         esac
154 }
155
156 _scratch_mkfs_encrypted_verity()
157 {
158         case $FSTYP in
159         ext4)
160                 _scratch_mkfs -O encrypt,verity
161                 ;;
162         f2fs)
163                 # f2fs-tools as of v1.11.0 doesn't allow comma-separated
164                 # features with -O.  Instead -O must be supplied multiple times.
165                 _scratch_mkfs -O encrypt -O verity
166                 ;;
167         *)
168                 _notrun "$FSTYP not supported in _scratch_mkfs_encrypted_verity"
169                 ;;
170         esac
171 }
172
173 _fsv_scratch_begin_subtest()
174 {
175         local msg=$1
176
177         rm -rf "${SCRATCH_MNT:?}"/*
178         echo -e "\n# $msg"
179 }
180
181 _fsv_dump_merkle_tree()
182 {
183         $FSVERITY_PROG dump_metadata merkle_tree "$@"
184 }
185
186 _fsv_dump_descriptor()
187 {
188         $FSVERITY_PROG dump_metadata descriptor "$@"
189 }
190
191 _fsv_dump_signature()
192 {
193         $FSVERITY_PROG dump_metadata signature "$@"
194 }
195
196 _fsv_enable()
197 {
198         $FSVERITY_PROG enable "$@"
199 }
200
201 _fsv_measure()
202 {
203         $FSVERITY_PROG measure "$@" | awk '{print $1}'
204 }
205
206 _fsv_sign()
207 {
208         $FSVERITY_PROG sign "$@"
209 }
210
211 # Generate a file, then enable verity on it.
212 _fsv_create_enable_file()
213 {
214         local file=$1
215         shift
216
217         head -c $((FSV_BLOCK_SIZE * 2)) /dev/zero > "$file"
218         _fsv_enable "$file" "$@"
219 }
220
221 _fsv_have_hash_algorithm()
222 {
223         local hash_alg=$1
224         local test_file=$2
225
226         rm -f $test_file
227         head -c 4096 /dev/zero > $test_file
228         if ! _fsv_enable --hash-alg=$hash_alg $test_file &>> $seqres.full; then
229                 # no kernel support
230                 return 1
231         fi
232         rm -f $test_file
233         return 0
234 }
235
236 #
237 # _fsv_scratch_corrupt_bytes - Write some bytes to a file, bypassing the filesystem
238 #
239 # Write the bytes sent on stdin to the given offset in the given file, but do so
240 # by writing directly to the extents on the block device, with the filesystem
241 # unmounted.  This can be used to corrupt a verity file for testing purposes,
242 # bypassing the restrictions imposed by the filesystem.
243 #
244 # The file is assumed to be located on $SCRATCH_DEV.
245 #
246 _fsv_scratch_corrupt_bytes()
247 {
248         local file=$1
249         local offset=$2
250         local lstart lend pstart pend
251         local dd_cmds=()
252         local cmd
253
254         sync    # Sync to avoid unwritten extents
255
256         cat > $tmp.bytes
257         local end=$(( offset + $(_get_filesize $tmp.bytes ) ))
258
259         # For each extent that intersects the requested range in order, add a
260         # command that writes the next part of the data to that extent.
261         while read -r lstart lend pstart pend; do
262                 lstart=$((lstart * 512))
263                 lend=$(((lend + 1) * 512))
264                 pstart=$((pstart * 512))
265                 pend=$(((pend + 1) * 512))
266
267                 if (( lend - lstart != pend - pstart )); then
268                         _fail "Logical and physical extent lengths differ for file '$file'"
269                 elif (( offset < lstart )); then
270                         _fail "Hole in file '$file' at byte $offset.  Next extent begins at byte $lstart"
271                 elif (( offset < lend )); then
272                         local len=$((lend - offset))
273                         local seek=$((pstart + (offset - lstart)))
274                         dd_cmds+=("head -c $len | dd of=$SCRATCH_DEV oflag=seek_bytes seek=$seek status=none")
275                         (( offset += len ))
276                 fi
277         done < <($XFS_IO_PROG -r -c "fiemap $offset $((end - offset))" "$file" \
278                  | _filter_xfs_io_fiemap)
279
280         if (( offset < end )); then
281                 _fail "Extents of file '$file' ended at byte $offset, but needed until $end"
282         fi
283
284         # Execute the commands to write the data
285         _scratch_unmount
286         for cmd in "${dd_cmds[@]}"; do
287                 eval "$cmd"
288         done < $tmp.bytes
289         sync    # Sync to flush the block device's pagecache
290         _scratch_mount
291 }
292
293 #
294 # _fsv_scratch_corrupt_merkle_tree - Corrupt a file's Merkle tree
295 #
296 # Like _fsv_scratch_corrupt_bytes(), but this corrupts the file's fs-verity
297 # Merkle tree.  The offset is given as a byte offset into the Merkle tree.
298 #
299 _fsv_scratch_corrupt_merkle_tree()
300 {
301         local file=$1
302         local offset=$2
303
304         case $FSTYP in
305         ext4|f2fs)
306                 # ext4 and f2fs store the Merkle tree after the file contents
307                 # itself, starting at the next 65536-byte aligned boundary.
308                 (( offset += ($(_get_filesize $file) + 65535) & ~65535 ))
309                 _fsv_scratch_corrupt_bytes $file $offset
310                 ;;
311         *)
312                 _fail "_fsv_scratch_corrupt_merkle_tree() unimplemented on $FSTYP"
313                 ;;
314         esac
315 }