Test IMA support for including fs-verity enabled file measurements in the IMA measurement list based on the ima-ngv2 and ima-sigv2 records. Signed-off-by: Mimi Zohar <zohar@xxxxxxxxxxxxx> --- Comment: - The fsverity.test executes locally, but fails to loopback mount a file in ci/travis. - Limit running the fsverity.test to Altlinux where it is installed and Fedora where it is compiled. build.sh | 1 + ci/alpine.sh | 2 + ci/alt.sh | 4 +- ci/debian.sh | 2 + ci/fedora.sh | 8 +- ci/tumbleweed.sh | 2 + tests/Makefile.am | 2 +- tests/fsverity.test | 303 ++++++++++++++++++++++++++++++++++++++ tests/install-fsverity.sh | 7 + 9 files changed, 328 insertions(+), 3 deletions(-) create mode 100755 tests/fsverity.test create mode 100755 tests/install-fsverity.sh diff --git a/build.sh b/build.sh index 0c2fdd9e995d..02dc15aeb369 100755 --- a/build.sh +++ b/build.sh @@ -98,6 +98,7 @@ if [ $ret -eq 0 ]; then grep "skipped" tests/sign_verify.log | wc -l fi tail -20 tests/boot_aggregate.log + tail -10 tests/fsverity.log exit 0 fi diff --git a/ci/alpine.sh b/ci/alpine.sh index 0e4ba0da6beb..6b17942a43fd 100755 --- a/ci/alpine.sh +++ b/ci/alpine.sh @@ -30,6 +30,7 @@ apk add \ diffutils \ docbook-xml \ docbook-xsl \ + e2fsprogs-extra \ keyutils-dev \ libtool \ libxslt \ @@ -41,6 +42,7 @@ apk add \ pkgconfig \ procps \ sudo \ + util-linux \ wget \ which \ xxd diff --git a/ci/alt.sh b/ci/alt.sh index 65389bef4487..36ff6579267c 100755 --- a/ci/alt.sh +++ b/ci/alt.sh @@ -11,7 +11,8 @@ apt-get install -y \ $TSS \ asciidoc \ attr \ - docbook-style-xsl \ + e2fsprogs \ + fsverity-utils-devel \ gnutls-utils \ libattr-devel \ libkeyutils-devel \ @@ -21,6 +22,7 @@ apt-get install -y \ openssl-gost-engine \ rpm-build \ softhsm \ + util-linux \ wget \ xsltproc \ xxd \ diff --git a/ci/debian.sh b/ci/debian.sh index 005b1f647ed8..431203aabb48 100755 --- a/ci/debian.sh +++ b/ci/debian.sh @@ -40,6 +40,7 @@ $apt \ debianutils \ docbook-xml \ docbook-xsl \ + e2fsprogs \ gzip \ libattr1-dev$ARCH \ libkeyutils-dev$ARCH \ @@ -50,6 +51,7 @@ $apt \ pkg-config \ procps \ sudo \ + util-linux \ wget \ xsltproc diff --git a/ci/fedora.sh b/ci/fedora.sh index 09936070ea93..2272bbc57fae 100755 --- a/ci/fedora.sh +++ b/ci/fedora.sh @@ -25,9 +25,12 @@ yum -y install \ automake \ diffutils \ docbook-xsl \ + e2fsprogs \ + git-core \ gnutls-utils \ gzip \ keyutils-libs-devel \ + kmod \ libattr-devel \ libtool \ libxslt \ @@ -38,6 +41,7 @@ yum -y install \ pkg-config \ procps \ sudo \ + util-linux \ vim-common \ wget \ which @@ -49,4 +53,6 @@ yum -y install swtpm || true if [ -f /etc/centos-release ]; then yum -y install epel-release fi -yum -y install softhsm || true \ No newline at end of file +yum -y install softhsm || true + +./tests/install-fsverity.sh diff --git a/ci/tumbleweed.sh b/ci/tumbleweed.sh index 4e3da0c6bb75..6f70b0fcc768 100755 --- a/ci/tumbleweed.sh +++ b/ci/tumbleweed.sh @@ -26,6 +26,7 @@ zypper --non-interactive install --force-resolution --no-recommends \ diffutils \ docbook_5 \ docbook5-xsl-stylesheets \ + e2fsprogs \ gzip \ ibmswtpm2 \ keyutils-devel \ @@ -37,6 +38,7 @@ zypper --non-interactive install --force-resolution --no-recommends \ pkg-config \ procps \ sudo \ + util-linux \ vim \ wget \ which \ diff --git a/tests/Makefile.am b/tests/Makefile.am index ff928e177406..feea235da873 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,7 +1,7 @@ check_SCRIPTS = TESTS = $(check_SCRIPTS) -check_SCRIPTS += ima_hash.test sign_verify.test boot_aggregate.test +check_SCRIPTS += ima_hash.test sign_verify.test boot_aggregate.test fsverity.test clean-local: -rm -f *.txt *.out *.sig *.sig2 diff --git a/tests/fsverity.test b/tests/fsverity.test new file mode 100755 index 000000000000..ad7762a479b7 --- /dev/null +++ b/tests/fsverity.test @@ -0,0 +1,303 @@ +#!/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. + +# 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)" + +source ./functions.sh +_require dd mkfs blkid e2fsck tune2fs evmctl setfattr +./gen-keys.sh >/dev/null 2>&1 + +trap 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 + _report_exit_and_cleanup +} + +# 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; then + echo "${RED}FAILURE: losetup${NORM}" + exit "$FAIL" + fi + + mount -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 "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 "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 "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 "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 "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 "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 "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 +} + +load_policy_rule() { + local test=$1 + local rule=$2 + + if ! get_current_uuid; then + echo "${RED}FAILURE:FAILED getting uuid${NORM}" + exit "$FAIL" + fi + + echo "$test: rule: $rule fsuuid=$UUID" + echo "$rule fsuuid=$UUID" > $IMA_POLICY_FILE +} + +create_file() { + local test=$1 + local type=$2 + + TST_FILE=$(mktemp -p $TST_MNT -t "${type}".XXXXXX) + [ "$VERBOSE" -ge 1 ] && echo "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"; then + echo "${RED}FAILURE: enabling fs-verity${NORM}" + exit "$FAIL" + fi + else + msg="measuring non fs-verity enabled file properly does not include digest $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 "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") + + 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} ${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" + + create_file "$test" ima-hash + "$TST_FILE" + + # sha256sum returns: <digest> <2 spaces> <filename> + # Remove the extra space before the filename + digest_filename=$(sha256sum "$TST_FILE" | sed "s/\ \ /\ /") + 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" +} + +# Skip the test if fsverity is not found; using _require fails the test. +if [ -z "$FSVERITY" ]; then + echo "${CYAN}fsverity is not installed${NORM}" + exit "$SKIP" +fi + +if [ "x$(id -u)" != "x0" ]; then + echo "${CYAN}Must be root to execute this test${NORM}" + exit "$SKIP" +fi + +create_loopback_file ext4 + +# IMA policy rule using the ima-ngv2 template +load_policy_rule test1 "measure func=BPRM_CHECK template=ima-ngv2" +expect_pass measure-ima test1 + +# fsverity IMA policy rule using the ima-ngv2 template +change_loopback_file_uuid +load_policy_rule test2 "measure func=BPRM_CHECK template=ima-ngv2 digest_type=verity" +expect_fail measure-verity test2 +expect_pass measure-verity test2 enabled + +# IMA policy rule using the ima-sigv2 template +change_loopback_file_uuid +load_policy_rule test3 "measure func=BPRM_CHECK template=ima-sigv2" +expect_pass measure-ima test3 + +# fsverity IMA policy rule using the ima-sigv2 template +change_loopback_file_uuid +load_policy_rule test4 "measure func=BPRM_CHECK template=ima-sigv2 digest_type=verity" + +expect_fail measure-verity test4 +expect_pass measure-verity test4 enabled +exit diff --git a/tests/install-fsverity.sh b/tests/install-fsverity.sh new file mode 100755 index 000000000000..418fc42f472b --- /dev/null +++ b/tests/install-fsverity.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +git clone https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git +cd fsverity-utils +CC=gcc make -j$(nproc) && sudo make install +cd .. +rm -rf fsverity-utils -- 2.31.1