1
0
mirror of https://git.code.sf.net/p/linux-ima/ima-evm-utils synced 2025-04-28 14:43:37 +02:00
ima-evm-utils-mirror/tests/fsverity.test
Roberto Sassu 03b5d159ca Pass cleanup function and its arguments to _report_exit_and_cleanup()
If an error occurs before any test is executed, _report_exit_and_cleanup()
returns 77 ($SKIP) as exit code, which might not reflect the real exit code
at the time the script terminated its execution.

If the function registered in the shell trap() is a cleanup function
calling _report_exit_and_cleanup() inside, the latter will not have access
to the exit code at the time of the trap but instead to the exit code of
the cleanup function.

To solve this issue, pass the cleanup function and its arguments to
_report_exit_and_cleanup(), so that the latter can first get the script
exit code and then can execute the cleanup function.

Finally, if no test was executed, return the exit code at the time of the
trap() instead of 77.

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:39:24 -05:00

377 lines
9.8 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:$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' 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"
}
# 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