1
0
mirror of https://git.code.sf.net/p/linux-ima/ima-evm-utils synced 2025-04-27 14:22:31 +02:00
ima-evm-utils-mirror/tests/fsverity.test
Roberto Sassu 452f4b2eac Use in-place built fsverity binary instead of installing it
Instead of making changes to the system, use in-place built fsverity binary
by adding ../fsverity-utils to the PATH variable, so that the binary can be
found with the 'command -v' command.

Don't delete the fsverity-utils directory, so that the built binary is
available. Not deleting should not be a problem, as the script is meant to
be executed in a CI environment, where cleanup is done by the CI
infrastructure itself.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2023-01-27 11:40:31 -05:00

386 lines
10 KiB
Bash
Executable File

#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Test IMA support for including fs-verity enabled files measurements
# in the IMA measurement list.
#
# Define policy rules showing the different types of IMA and fs-verity
# records in the IMA measurement list. Include examples of files that
# are suppose to be fs-verity enabled, but aren't.
#
# test 1: IMA policy rule using the new ima-ngv2 template
# - Hash prefixed with "ima:"
#
# test 2: fs-verity IMA policy rule using the new ima-ngv2 template
# - fs-verity hash prefixed with "verity:"
# - Non fs-verity enabled file, zeros prefixed with "verity:"
#
# test 3: IMA policy rule using the new ima-sigv2 template
# - Hash prefixed with "ima:"
# - Appended signature, when available.
#
# test 4: fs-verity IMA policy rule using the new ima-sigv2 template
# - fs-verity hash prefixed with "verity:"
# - Non fs-verity enabled file, zeros prefixed with "verity:"
# - Appended IMA signature of fs-verity file hash, when available.
# To avoid affecting the system's IMA custom policy or requiring a
# reboot between tests, define policy rules based on UUID. However,
# since the policy rules are walked sequentially, the system's IMA
# custom policy rules might take precedence.
cd "$(dirname "$0")" || exit 1
PATH=../src:../fsverity-utils:$PATH
source ./functions.sh
# Base VERBOSE on the environment variable, if set.
VERBOSE="${VERBOSE:-0}"
IMA_POLICY_FILE="/sys/kernel/security/integrity/ima/policy"
IMA_MEASUREMENT_LIST="/sys/kernel/security/integrity/ima/ascii_runtime_measurements"
TST_MNT="/tmp/fsverity-test"
TST_IMG="/tmp/test.img"
LOOPBACK_MOUNTED=0
FSVERITY="$(which fsverity)"
_require dd mkfs blkid e2fsck tune2fs evmctl setfattr
./gen-keys.sh >/dev/null 2>&1
trap '_report_exit_and_cleanup _cleanup_env cleanup' SIGINT SIGTERM EXIT
cleanup() {
if [ -e $TST_MNT ]; then
if [ $LOOPBACK_MOUNTED -eq 1 ]; then
umount $TST_MNT
fi
if [ -f "$TST_IMG" ]; then
rm "$TST_IMG"
fi
fi
}
# Loopback mount a file
mount_loopback_file() {
local ret
if [ ! -d $TST_MNT ]; then
mkdir $TST_MNT
fi
# if modprobe loop; then
# echo "${CYAN}INFO: modprobe loop failed${NORM}"
# fi
if ! losetup -f &> /dev/null; then
echo "${RED}FAILURE: losetup${NORM}"
exit "$FAIL"
fi
mount -v -o loop ${TST_IMG} $TST_MNT
ret=$?
if [ "${ret}" -eq 0 ]; then
LOOPBACK_MOUNTED=1
fi
return "$ret"
}
# Change the loopback mounted filesystem's UUID in between tests
change_loopback_file_uuid() {
echo " "
[ "$VERBOSE" -ge 1 ] && echo "INFO: Changing loopback file uuid"
umount $TST_MNT
if ! e2fsck -y -f ${TST_IMG} &> /dev/null; then
echo "${RED}FAILURE: e2fsck${NORM}"
exit "$FAIL"
fi
if ! tune2fs -f ${TST_IMG} -U random &> /dev/null; then
echo "${RED}FAILURE: change UUID${NORM}"
exit "$FAIL"
fi
[ "$VERBOSE" -ge 1 ] && echo "INFO: Remounting loopback filesystem"
if ! mount_loopback_file; then
echo "${RED}FAILURE: re-mounting loopback filesystem${NORM}"
exit "$FAIL"
fi
return 0
}
# Create a file to be loopback mounted
create_loopback_file() {
local fs_type=$1
local options=""
echo "INFO: Creating loopback filesystem"
case $fs_type in
ext4|f2fs)
options="-O verity"
;;
btrfs)
;;
*)
echo "${RED}FAILURE: unsupported fs-verity filesystem${NORM}"
exit "${FAIL}"
;;
esac
[ "$VERBOSE" -ge 2 ] && echo "INFO: Creating a file to be loopback mounted with options: $options"
if ! dd if=/dev/zero of="${TST_IMG}" bs=100M count=6 &> /dev/null; then
echo "${RED}FAILURE: creating ${TST_IMG}${NORM}"
exit "$FAIL"
fi
echo "INFO: Building an $fs_type filesystem"
if ! mkfs -t "$fs_type" -q "${TST_IMG}" "$options"; then
echo "${RED}FAILURE: Creating $fs_type filesystem${NORM}"
exit "$FAIL"
fi
echo "INFO: Mounting loopback filesystem"
if ! mount_loopback_file; then
echo "${RED}FAILURE: mounting loopback filesystem${NORM}"
exit "$FAIL"
fi
return 0
}
get_current_uuid() {
[ "$VERBOSE" -ge 2 ] && echo "INFO: Getting loopback file uuid"
if ! UUID=$(blkid -s UUID -o value ${TST_IMG}); then
echo "${RED}FAILURE: to get UUID${NORM}"
return "$FAIL"
fi
return 0
}
unqualified_bprm_rule() {
local test=$1
local rule=$2
local rule_match="measure func=BPRM_CHECK"
local rule_dontmatch="fsuuid"
if [ -z "${rule##*$digest_type=verity*}" ]; then
if grep "$rule_match" $IMA_POLICY_FILE | grep -v "$rule_dontmatch" &> /dev/null; then
return "$SKIP"
fi
fi
return 0
}
load_policy_rule() {
local test=$1
local rule=$2
if ! get_current_uuid; then
echo "${RED}FAILURE:FAILED getting uuid${NORM}"
exit "$FAIL"
fi
unqualified_bprm_rule "${test}" "${rule}"
if [ $? -eq "${SKIP}" ]; then
echo "${CYAN}SKIP: fsuuid unqualified \"BPRM_CHECK\" rule exists${NORM}"
return "$SKIP"
fi
echo "$test: rule: $rule fsuuid=$UUID"
if ! echo "$rule fsuuid=$UUID" > $IMA_POLICY_FILE; then
echo "${CYAN}SKIP: Loading policy rule failed, skipping test${NORM}"
return "$SKIP"
fi
return 0
}
create_file() {
local test=$1
local type=$2
TST_FILE=$(mktemp -p $TST_MNT -t "${type}".XXXXXX)
[ "$VERBOSE" -ge 1 ] && echo "INFO: creating $TST_FILE"
# heredoc to create a script
cat <<-EOF > "$TST_FILE"
#!/bin/bash
echo "Hello" &> /dev/null
EOF
chmod a+x "$TST_FILE"
}
measure-verity() {
local test=$1
local verity="${2:-disabled}"
local digest_filename
local error="$OK"
local KEY=$PWD/test-rsa2048.key
create_file "$test" verity-hash
if [ "$verity" = "enabled" ]; then
msg="Measuring fs-verity enabled file $TST_FILE"
if ! "$FSVERITY" enable "$TST_FILE" &> /dev/null; then
echo "${CYAN}SKIP: Failed enabling fs-verity on $TST_FILE${NORM}"
return "$SKIP"
fi
else
msg="Measuring non fs-verity enabled file $TST_FILE"
fi
# Sign the fsverity digest and write it as security.ima xattr.
# "evmctl sign_hash" input: <digest> <filename>
# "evmctl sign_hash" output: <digest> <filename> <signature>
[ "$VERBOSE" -ge 2 ] && echo "INFO: Signing the fsverity digest"
xattr=$("$FSVERITY" digest "$TST_FILE" | evmctl sign_hash --veritysig --key "$KEY" 2> /dev/null)
sig=$(echo "$xattr" | cut -d' ' -f3)
# On failure to write security.ima xattr, the signature will simply
# not be appended to the measurement list record.
if ! setfattr -n security.ima -v "0x$sig" "$TST_FILE"; then
echo "${CYAN}INFO: Failed to write security.ima xattr${NORM}"
fi
"$TST_FILE"
# "fsverity digest" calculates the fsverity hash, even for
# non fs-verity enabled files.
digest_filename=$("$FSVERITY" digest "$TST_FILE")
[ "$VERBOSE" -ge 2 ] && echo "INFO: verity:$digest_filename"
grep "verity:$digest_filename" $IMA_MEASUREMENT_LIST &> /dev/null
ret=$?
# Not finding the "fsverity digest" result in the IMA measurement
# list is expected for non fs-verity enabled files. The measurement
# list will contain zeros for the file hash.
if [ $ret -eq 1 ]; then
error="$FAIL"
if [ "$verity" = "enabled" ]; then
echo "${RED}FAILURE: ${msg} ${NORM}"
else
echo "${GREEN}SUCCESS: ${msg}, fsverity digest not found${NORM}"
fi
else
if [ "$verity" = "enabled" ]; then
echo "${GREEN}SUCCESS: ${msg} ${NORM}"
else
error="$FAIL"
echo "${RED}FAILURE: ${msg} ${NORM}"
fi
fi
return "$error"
}
measure-ima() {
local test=$1
local digest_filename
local error="$OK"
local hashalg
local digestsum
create_file "$test" ima-hash
"$TST_FILE"
hashalg=$(grep "${TST_FILE}" $IMA_MEASUREMENT_LIST | cut -d':' -f2)
if [ -z "${hashalg}" ]; then
echo "${CYAN}SKIP: Measurement record with algorithm not found${NORM}"
return "$SKIP"
fi
digestsum=$(which "${hashalg}"sum)
if [ -z "${digestsum}" ]; then
echo "${CYAN}SKIP: ${hashalg}sum is not installed${NORM}"
return "$SKIP"
fi
# sha1sum,sha256sum return: <digest> <2 spaces> <filename>
# Remove the extra space before the filename
digest_filename=$(${digestsum} "$TST_FILE" | sed "s/\ \ /\ /")
[ "$VERBOSE" -ge 2 ] && echo "$test: $digest_filename"
if grep "$digest_filename" $IMA_MEASUREMENT_LIST &> /dev/null; then
echo "${GREEN}SUCCESS: Measuring $TST_FILE ${NORM}"
else
error="$FAIL"
echo "${RED}FAILURE: Measuring $TST_FILE ${NORM}"
fi
return "$error"
}
# Run in the new environment if TST_ENV is set.
_run_env "$TST_KERNEL" "$PWD/$(basename "$0")" "TST_ENV=$TST_ENV TST_KERNEL=$TST_KERNEL PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH VERBOSE=$VERBOSE"
# Exit from the creator of the new environment.
_exit_env "$TST_KERNEL"
# Mount filesystems in the new environment.
_init_env
# Dependency on being able to read and write the IMA policy file.
# Requires both CONFIG_IMA_WRITE_POLICY, CONFIG_IMA_READ_POLICY be
# enabled.
if [ -e "$IMA_POLICY_FILE" ]; then
mode=$(stat -c "%a" $IMA_POLICY_FILE)
if [ "$mode" != "600" ]; then
echo "${CYAN}SKIP: IMA policy file must be read-write${NORM}"
exit "$SKIP"
fi
else
echo "${CYAN}SKIP: $IMA_POLICY_FILE does not exist${NORM}"
exit "$SKIP"
fi
# Skip the test if fsverity is not found; using _require fails the test.
if [ -z "$FSVERITY" ]; then
echo "${CYAN}SKIP: fsverity is not installed${NORM}"
exit "$SKIP"
fi
if [ "x$(id -u)" != "x0" ]; then
echo "${CYAN}SKIP: Must be root to execute this test${NORM}"
exit "$SKIP"
fi
create_loopback_file ext4
# Commit 989dc72511f7 ("ima: define a new template field named 'd-ngv2' and
# templates") introduced ima-ngv2 and ima-sigv2 in linux-5.19.
__skip() { return "$SKIP"; }
# IMA policy rule using the ima-ngv2 template
if load_policy_rule test1 "measure func=BPRM_CHECK template=ima-ngv2"; then
expect_pass measure-ima test1
else
expect_pass __skip
fi
# fsverity IMA policy rule using the ima-ngv2 template
change_loopback_file_uuid
if load_policy_rule test2 "measure func=BPRM_CHECK template=ima-ngv2 digest_type=verity"; then
expect_fail measure-verity test2
expect_pass measure-verity test2 enabled
else
expect_pass __skip
expect_pass __skip
fi
# IMA policy rule using the ima-sigv2 template
change_loopback_file_uuid
if load_policy_rule test3 "measure func=BPRM_CHECK template=ima-sigv2"; then
expect_pass measure-ima test3
else
expect_pass __skip
fi
# fsverity IMA policy rule using the ima-sigv2 template
change_loopback_file_uuid
if load_policy_rule test4 "measure func=BPRM_CHECK template=ima-sigv2 digest_type=verity"; then
expect_fail measure-verity test4
expect_pass measure-verity test4 enabled
else
expect_pass __skip
expect_pass __skip
fi
exit