mirror of
https://git.code.sf.net/p/linux-ima/ima-evm-utils
synced 2025-04-27 14:22:31 +02:00

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>
386 lines
10 KiB
Bash
Executable File
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
|