From: Eric Biggers <ebiggers@xxxxxxxxxx> Add common functions for setting up and testing fs-verity, a new feature for read-only file-based authenticity protection. fs-verity will be supported by ext4 and f2fs, and perhaps other filesystems later. Running the fs-verity tests requires: - A kernel with the fs-verity patches from https://git.kernel.org/pub/scm/linux/kernel/git/tytso/fscrypt.git/log/ (should be merged in 4.21) and configured with CONFIG_FS_VERITY. - The fsverity utility program, which can be installed from https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git/ - e2fsprogs v1.44.4-2 or later for ext4 tests, or f2fs-tools v1.11.0 or later for f2fs tests. See the file Documentation/filesystem/fsverity.rst in the kernel tree for more information about fs-verity. Signed-off-by: Eric Biggers <ebiggers@xxxxxxxxxx> --- common/config | 1 + common/verity | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 common/verity diff --git a/common/config b/common/config index a87cb4a2..b2160667 100644 --- a/common/config +++ b/common/config @@ -194,6 +194,7 @@ export GETCAP_PROG="$(type -P getcap)" export CHECKBASHISMS_PROG="$(type -P checkbashisms)" export XFS_INFO_PROG="$(type -P xfs_info)" export DUPEREMOVE_PROG="$(type -P duperemove)" +export FSVERITY_PROG="$(type -P fsverity)" # use 'udevadm settle' or 'udevsettle' to wait for lv to be settled. # newer systems have udevadm command but older systems like RHEL5 don't. diff --git a/common/verity b/common/verity new file mode 100644 index 00000000..4da63b69 --- /dev/null +++ b/common/verity @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2018 Google LLC +# +# Functions for setting up and testing fs-verity + +FSV_BLOCK_SIZE=4096 + +_require_scratch_verity() +{ + _require_scratch + _require_command "$FSVERITY_PROG" fsverity + + if ! _scratch_mkfs_verity &>>$seqres.full; then + # ext4: need e2fsprogs v1.44.4-2 or later + # 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 the filesystem is + # unaware of the fs-verity feature. + if ! _try_scratch_mount &>>$seqres.full; then + _notrun "kernel doesn't know about $FSTYP verity feature" + fi + _scratch_unmount + + # 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 + + # fs-verity with block_size != PAGE_SIZE isn't implemented yet. + # ("block_size" here refers to the fs-verity block size, not to the + # filesystem's block size.) + if [ "$(getconf PAGE_SIZE)" != $FSV_BLOCK_SIZE ]; then + _notrun "verity not yet supported for PAGE_SIZE != $FSV_BLOCK_SIZE" + fi +} + +_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_randstring() +{ + local nchars=$1 + + tr -d -C 0-9a-f < /dev/urandom | head -c "$nchars" +} + +_fsv_begin_subtest() +{ + local msg=$1 + + rm -rf "${SCRATCH_MNT:?}"/* + echo -e "\n# $msg" +} + +_fsv_setup() +{ + $FSVERITY_PROG setup "$@" | awk '/^File measurement: /{print $3}' +} + +_fsv_enable() +{ + $FSVERITY_PROG enable "$@" +} + +_fsv_measure() +{ + $FSVERITY_PROG measure "$@" | awk '{print $1}' +} + +# Generate a file with verity metadata, but don't actually enable verity yet +_fsv_create_setup_file() +{ + local file=$1 + + head -c $((FSV_BLOCK_SIZE * 2)) /dev/zero > "$file" + _fsv_setup "$file" +} + +# Generate a file with verity metadata, then enable verity +_fsv_create_enable_file() +{ + local file=$1 + + _fsv_create_setup_file "$file" + _fsv_enable "$file" +} + +# +# _fsv_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. On ext4 and f2fs this +# can also write into the metadata region of a verity file. +# +# The file is assumed to be located on $SCRATCH_DEV. +# +_fsv_corrupt_bytes() +{ + local file=$1 + local offset=$2 + local lstarts=() # extent logical starts, in bytes + local pstarts=() # extent physical starts, in bytes + local lens=() # extent lengths, in bytes + local line + local cmd + local dd_cmds=() + local eidx=0 + + sync # Sync to avoid unwritten extents + + cat > $tmp.bytes + local end=$(( offset + $(stat -c %s $tmp.bytes ) )) + + # Get the list of extents that intersect the requested range + while read -r line; do \ + local fields=($line) + local lstart=${fields[0]} + local lend=${fields[1]} + local pstart=${fields[2]} + local pend=${fields[3]} + local llen=$((lend + 1 - lstart)) + local plen=$((pend + 1 - pstart)) + if (( llen != plen )); then + _fail "Logical and physical extent lengths differ! $line" + fi + lstarts+=( $((lstart * 512)) ) + pstarts+=( $((pstart * 512)) ) + lens+=( $((llen * 512)) ) + done < <($XFS_IO_PROG -r -c "fiemap $offset $((end - offset))" "$file" \ + | grep -E '^[[:space:]]+[0-9]+:' \ + | grep -v '\<hole\>' \ + | sed -E 's/^[[:space:]]+[0-9]+://' \ + | tr '][.:' ' ') + + while (( offset < end )); do + # Find the next extent to write to + while true; do + if (( eidx >= ${#lstarts[@]} )); then + _fail "Extents ended before byte $offset" + fi + if (( offset < ${lstarts[$eidx]} )); then + _fail "Hole in file at byte $offset" + fi + local lend=$(( ${lstarts[$eidx]} + ${lens[$eidx]} )) + if (( offset < lend )); then + break + fi + (( eidx += 1 )) + done + # Add a command that writes to the next extent + local len=$((lend - offset)) + local seek=$(( offset + ${pstarts[$eidx]} - ${lstarts[$eidx]} )) + if (( len > end - offset )); then + len=$((end - offset)) + fi + dd_cmds+=("head -c $len | dd of=$SCRATCH_DEV oflag=seek_bytes seek=$seek status=none") + (( offset += len )) + done + + # 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 +} -- 2.20.0.rc2.403.gdbc3b29805-goog