| # 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 | 
 | } |