generic: factor out helpers for fs-verity built-in signatures
[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 _scratch_mkfs_verity()
124 {
125         case $FSTYP in
126         ext4|f2fs)
127                 _scratch_mkfs -O verity
128                 ;;
129         *)
130                 _notrun "No verity support for $FSTYP"
131                 ;;
132         esac
133 }
134
135 _scratch_mkfs_encrypted_verity()
136 {
137         case $FSTYP in
138         ext4)
139                 _scratch_mkfs -O encrypt,verity
140                 ;;
141         f2fs)
142                 # f2fs-tools as of v1.11.0 doesn't allow comma-separated
143                 # features with -O.  Instead -O must be supplied multiple times.
144                 _scratch_mkfs -O encrypt -O verity
145                 ;;
146         *)
147                 _notrun "$FSTYP not supported in _scratch_mkfs_encrypted_verity"
148                 ;;
149         esac
150 }
151
152 _fsv_scratch_begin_subtest()
153 {
154         local msg=$1
155
156         rm -rf "${SCRATCH_MNT:?}"/*
157         echo -e "\n# $msg"
158 }
159
160 _fsv_enable()
161 {
162         $FSVERITY_PROG enable "$@"
163 }
164
165 _fsv_measure()
166 {
167         $FSVERITY_PROG measure "$@" | awk '{print $1}'
168 }
169
170 _fsv_sign()
171 {
172         $FSVERITY_PROG sign "$@"
173 }
174
175 # Generate a file, then enable verity on it.
176 _fsv_create_enable_file()
177 {
178         local file=$1
179         shift
180
181         head -c $((FSV_BLOCK_SIZE * 2)) /dev/zero > "$file"
182         _fsv_enable "$file" "$@"
183 }
184
185 _fsv_have_hash_algorithm()
186 {
187         local hash_alg=$1
188         local test_file=$2
189
190         rm -f $test_file
191         head -c 4096 /dev/zero > $test_file
192         if ! _fsv_enable --hash-alg=$hash_alg $test_file &>> $seqres.full; then
193                 # no kernel support
194                 return 1
195         fi
196         rm -f $test_file
197         return 0
198 }
199
200 #
201 # _fsv_scratch_corrupt_bytes - Write some bytes to a file, bypassing the filesystem
202 #
203 # Write the bytes sent on stdin to the given offset in the given file, but do so
204 # by writing directly to the extents on the block device, with the filesystem
205 # unmounted.  This can be used to corrupt a verity file for testing purposes,
206 # bypassing the restrictions imposed by the filesystem.
207 #
208 # The file is assumed to be located on $SCRATCH_DEV.
209 #
210 _fsv_scratch_corrupt_bytes()
211 {
212         local file=$1
213         local offset=$2
214         local lstart lend pstart pend
215         local dd_cmds=()
216         local cmd
217
218         sync    # Sync to avoid unwritten extents
219
220         cat > $tmp.bytes
221         local end=$(( offset + $(_get_filesize $tmp.bytes ) ))
222
223         # For each extent that intersects the requested range in order, add a
224         # command that writes the next part of the data to that extent.
225         while read -r lstart lend pstart pend; do
226                 lstart=$((lstart * 512))
227                 lend=$(((lend + 1) * 512))
228                 pstart=$((pstart * 512))
229                 pend=$(((pend + 1) * 512))
230
231                 if (( lend - lstart != pend - pstart )); then
232                         _fail "Logical and physical extent lengths differ for file '$file'"
233                 elif (( offset < lstart )); then
234                         _fail "Hole in file '$file' at byte $offset.  Next extent begins at byte $lstart"
235                 elif (( offset < lend )); then
236                         local len=$((lend - offset))
237                         local seek=$((pstart + (offset - lstart)))
238                         dd_cmds+=("head -c $len | dd of=$SCRATCH_DEV oflag=seek_bytes seek=$seek status=none")
239                         (( offset += len ))
240                 fi
241         done < <($XFS_IO_PROG -r -c "fiemap $offset $((end - offset))" "$file" \
242                  | _filter_xfs_io_fiemap)
243
244         if (( offset < end )); then
245                 _fail "Extents of file '$file' ended at byte $offset, but needed until $end"
246         fi
247
248         # Execute the commands to write the data
249         _scratch_unmount
250         for cmd in "${dd_cmds[@]}"; do
251                 eval "$cmd"
252         done < $tmp.bytes
253         sync    # Sync to flush the block device's pagecache
254         _scratch_mount
255 }
256
257 #
258 # _fsv_scratch_corrupt_merkle_tree - Corrupt a file's Merkle tree
259 #
260 # Like _fsv_scratch_corrupt_bytes(), but this corrupts the file's fs-verity
261 # Merkle tree.  The offset is given as a byte offset into the Merkle tree.
262 #
263 _fsv_scratch_corrupt_merkle_tree()
264 {
265         local file=$1
266         local offset=$2
267
268         case $FSTYP in
269         ext4|f2fs)
270                 # ext4 and f2fs store the Merkle tree after the file contents
271                 # itself, starting at the next 65536-byte aligned boundary.
272                 (( offset += ($(_get_filesize $file) + 65535) & ~65535 ))
273                 _fsv_scratch_corrupt_bytes $file $offset
274                 ;;
275         *)
276                 _fail "_fsv_scratch_corrupt_merkle_tree() unimplemented on $FSTYP"
277                 ;;
278         esac
279 }