blob: 38eea157cbc53418e97aeb16a8e0314378d732ed [file] [log] [blame]
# SPDX-License-Identifier: GPL-2.0
# Copyright 2018 Google LLC
#
# Functions for setting up and testing fs-verity
_require_scratch_verity()
{
_require_scratch
_require_command "$FSVERITY_PROG" fsverity
if ! _scratch_mkfs_verity &>>$seqres.full; then
# ext4: need e2fsprogs v1.44.5 or later (but actually v1.45.2+
# is needed for some tests to pass, due to an e2fsck bug)
# f2fs: need f2fs-tools v1.11.0 or later
_notrun "$FSTYP userspace tools don't support fs-verity"
fi
# Try to mount the filesystem. If this fails then either the kernel
# isn't aware of fs-verity, or the mkfs options were not compatible with
# verity (e.g. ext4 with block size != PAGE_SIZE).
if ! _try_scratch_mount &>>$seqres.full; then
_notrun "kernel is unaware of $FSTYP verity feature," \
"or mkfs options are not compatible with verity"
fi
# The filesystem may be aware of fs-verity but have it disabled by
# CONFIG_FS_VERITY=n. Detect support via sysfs.
if [ ! -e /sys/fs/$FSTYP/features/verity ]; then
_notrun "kernel $FSTYP isn't configured with verity support"
fi
# The filesystem may have fs-verity enabled but not actually usable by
# default. E.g., ext4 only supports verity on extent-based files, so it
# doesn't work on ext3-style filesystems. So, try actually using it.
echo foo > $SCRATCH_MNT/tmpfile
_disable_fsverity_signatures
if ! _fsv_enable $SCRATCH_MNT/tmpfile; then
_restore_fsverity_signatures
_notrun "$FSTYP verity isn't usable by default with these mkfs options"
fi
_restore_fsverity_signatures
rm -f $SCRATCH_MNT/tmpfile
_scratch_unmount
# Merkle tree block size. Currently all filesystems only support
# PAGE_SIZE for this. This is also the default for 'fsverity enable'.
FSV_BLOCK_SIZE=$(get_page_size)
}
# Check for CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y, as well as the userspace
# commands needed to generate certificates and add them to the kernel.
_require_fsverity_builtin_signatures()
{
if [ ! -e /proc/sys/fs/verity/require_signatures ]; then
_notrun "kernel doesn't support fs-verity builtin signatures"
fi
_require_command "$OPENSSL_PROG" openssl
_require_command "$KEYCTL_PROG" keyctl
}
# Use the openssl program to generate a private key and a X.509 certificate for
# use with fs-verity built-in signature verification, and convert the
# certificate to DER format.
_fsv_generate_cert()
{
local keyfile=$1
local certfile=$2
local certfileder=$3
if ! $OPENSSL_PROG req -newkey rsa:4096 -nodes -batch -x509 \
-keyout $keyfile -out $certfile &>> $seqres.full; then
_fail "Failed to generate certificate and private key (see $seqres.full)"
fi
$OPENSSL_PROG x509 -in $certfile -out $certfileder -outform der
}
# Clear the .fs-verity keyring.
_fsv_clear_keyring()
{
$KEYCTL_PROG clear %keyring:.fs-verity
}
# Load the given X.509 certificate in DER format into the .fs-verity keyring so
# that the kernel can use it to verify built-in signatures.
_fsv_load_cert()
{
local certfileder=$1
$KEYCTL_PROG padd asymmetric '' %keyring:.fs-verity \
< $certfileder >> $seqres.full
}
# Disable mandatory signatures for fs-verity files, if they are supported.
_disable_fsverity_signatures()
{
if [ -e /proc/sys/fs/verity/require_signatures ]; then
if [ -z "$FSVERITY_SIG_CTL_ORIG" ]; then
FSVERITY_SIG_CTL_ORIG=$(</proc/sys/fs/verity/require_signatures)
fi
echo 0 > /proc/sys/fs/verity/require_signatures
fi
}
# Enable mandatory signatures for fs-verity files.
# This assumes that _require_fsverity_builtin_signatures() was called.
_enable_fsverity_signatures()
{
if [ -z "$FSVERITY_SIG_CTL_ORIG" ]; then
FSVERITY_SIG_CTL_ORIG=$(</proc/sys/fs/verity/require_signatures)
fi
echo 1 > /proc/sys/fs/verity/require_signatures
}
# Restore the original signature verification setting.
_restore_fsverity_signatures()
{
if [ -n "$FSVERITY_SIG_CTL_ORIG" ]; then
echo "$FSVERITY_SIG_CTL_ORIG" > /proc/sys/fs/verity/require_signatures
fi
}
# Require userspace and kernel support for 'fsverity dump_metadata'.
# $1 must be a file with fs-verity enabled.
_require_fsverity_dump_metadata()
{
local verity_file=$1
local tmpfile=$tmp.require_fsverity_dump_metadata
if _fsv_dump_merkle_tree "$verity_file" 2>"$tmpfile" >/dev/null; then
return
fi
if grep -q "^ERROR: unrecognized command: 'dump_metadata'$" "$tmpfile"
then
_notrun "Missing 'fsverity dump_metadata' command"
fi
if grep -q "^ERROR: FS_IOC_READ_VERITY_METADATA failed on '.*': Inappropriate ioctl for device$" "$tmpfile"
then
_notrun "Kernel doesn't support FS_IOC_READ_VERITY_METADATA"
fi
_fail "Unexpected output from 'fsverity dump_metadata': $(<"$tmpfile")"
}
_scratch_mkfs_verity()
{
case $FSTYP in
ext4|f2fs)
_scratch_mkfs -O verity
;;
*)
_notrun "No verity support for $FSTYP"
;;
esac
}
_scratch_mkfs_encrypted_verity()
{
case $FSTYP in
ext4)
_scratch_mkfs -O encrypt,verity
;;
f2fs)
# f2fs-tools as of v1.11.0 doesn't allow comma-separated
# features with -O. Instead -O must be supplied multiple times.
_scratch_mkfs -O encrypt -O verity
;;
*)
_notrun "$FSTYP not supported in _scratch_mkfs_encrypted_verity"
;;
esac
}
_fsv_scratch_begin_subtest()
{
local msg=$1
rm -rf "${SCRATCH_MNT:?}"/*
echo -e "\n# $msg"
}
_fsv_dump_merkle_tree()
{
$FSVERITY_PROG dump_metadata merkle_tree "$@"
}
_fsv_dump_descriptor()
{
$FSVERITY_PROG dump_metadata descriptor "$@"
}
_fsv_dump_signature()
{
$FSVERITY_PROG dump_metadata signature "$@"
}
_fsv_enable()
{
$FSVERITY_PROG enable "$@"
}
_fsv_measure()
{
$FSVERITY_PROG measure "$@" | awk '{print $1}'
}
_fsv_sign()
{
$FSVERITY_PROG sign "$@"
}
# Generate a file, then enable verity on it.
_fsv_create_enable_file()
{
local file=$1
shift
head -c $((FSV_BLOCK_SIZE * 2)) /dev/zero > "$file"
_fsv_enable "$file" "$@"
}
_fsv_have_hash_algorithm()
{
local hash_alg=$1
local test_file=$2
rm -f $test_file
head -c 4096 /dev/zero > $test_file
if ! _fsv_enable --hash-alg=$hash_alg $test_file &>> $seqres.full; then
# no kernel support
return 1
fi
rm -f $test_file
return 0
}
#
# _fsv_scratch_corrupt_bytes - Write some bytes to a file, bypassing the filesystem
#
# Write the bytes sent on stdin to the given offset in the given file, but do so
# by writing directly to the extents on the block device, with the filesystem
# unmounted. This can be used to corrupt a verity file for testing purposes,
# bypassing the restrictions imposed by the filesystem.
#
# The file is assumed to be located on $SCRATCH_DEV.
#
_fsv_scratch_corrupt_bytes()
{
local file=$1
local offset=$2
local lstart lend pstart pend
local dd_cmds=()
local cmd
sync # Sync to avoid unwritten extents
cat > $tmp.bytes
local end=$(( offset + $(_get_filesize $tmp.bytes ) ))
# For each extent that intersects the requested range in order, add a
# command that writes the next part of the data to that extent.
while read -r lstart lend pstart pend; do
lstart=$((lstart * 512))
lend=$(((lend + 1) * 512))
pstart=$((pstart * 512))
pend=$(((pend + 1) * 512))
if (( lend - lstart != pend - pstart )); then
_fail "Logical and physical extent lengths differ for file '$file'"
elif (( offset < lstart )); then
_fail "Hole in file '$file' at byte $offset. Next extent begins at byte $lstart"
elif (( offset < lend )); then
local len=$((lend - offset))
local seek=$((pstart + (offset - lstart)))
dd_cmds+=("head -c $len | dd of=$SCRATCH_DEV oflag=seek_bytes seek=$seek status=none")
(( offset += len ))
fi
done < <($XFS_IO_PROG -r -c "fiemap $offset $((end - offset))" "$file" \
| _filter_xfs_io_fiemap)
if (( offset < end )); then
_fail "Extents of file '$file' ended at byte $offset, but needed until $end"
fi
# Execute the commands to write the data
_scratch_unmount
for cmd in "${dd_cmds[@]}"; do
eval "$cmd"
done < $tmp.bytes
sync # Sync to flush the block device's pagecache
_scratch_mount
}
#
# _fsv_scratch_corrupt_merkle_tree - Corrupt a file's Merkle tree
#
# Like _fsv_scratch_corrupt_bytes(), but this corrupts the file's fs-verity
# Merkle tree. The offset is given as a byte offset into the Merkle tree.
#
_fsv_scratch_corrupt_merkle_tree()
{
local file=$1
local offset=$2
case $FSTYP in
ext4|f2fs)
# ext4 and f2fs store the Merkle tree after the file contents
# itself, starting at the next 65536-byte aligned boundary.
(( offset += ($(_get_filesize $file) + 65535) & ~65535 ))
_fsv_scratch_corrupt_bytes $file $offset
;;
*)
_fail "_fsv_scratch_corrupt_merkle_tree() unimplemented on $FSTYP"
;;
esac
}