@@ -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
@@ -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
@@ -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 \
@@ -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
@@ -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
@@ -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 \
@@ -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
new file mode 100755
@@ -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
new file mode 100755
@@ -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
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@linux.ibm.com> --- 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