blob: df3fc5603fda2ad96b93af60c4ac0ea91c4080d3 [file] [log] [blame]
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (c) 2009 IBM Corporation
# Copyright (c) 2018-2020 Petr Vorel <pvorel@suse.cz>
# Author: Mimi Zohar <zohar@linux.ibm.com>
TST_TESTFUNC="test"
TST_SETUP_CALLER="$TST_SETUP"
TST_SETUP="ima_setup"
TST_CLEANUP_CALLER="$TST_CLEANUP"
TST_CLEANUP="ima_cleanup"
TST_NEEDS_ROOT=1
TST_MOUNT_DEVICE=1
# TST_MOUNT_DEVICE can be unset, therefore specify explicitly
TST_NEEDS_TMPDIR=1
SYSFS="/sys"
UMOUNT=
TST_FS_TYPE="ext3"
# TODO: find support for rmd128 rmd256 rmd320 wp256 wp384 tgr128 tgr160
compute_digest()
{
local algorithm="$1"
local file="$2"
local digest
digest="$(${algorithm}sum $file 2>/dev/null | cut -f1 -d ' ')"
if [ -n "$digest" ]; then
echo "$digest"
return 0
fi
digest="$(openssl $algorithm $file 2>/dev/null | cut -f2 -d ' ')"
if [ -n "$digest" ]; then
echo "$digest"
return 0
fi
# uncommon ciphers
local arg="$algorithm"
case "$algorithm" in
tgr192) arg="tiger" ;;
wp512) arg="whirlpool" ;;
esac
digest="$(rdigest --$arg $file 2>/dev/null | cut -f1 -d ' ')"
if [ -n "$digest" ]; then
echo "$digest"
return 0
fi
return 1
}
check_policy_readable()
{
if [ ! -f $IMA_POLICY ]; then
tst_res TINFO "missing $IMA_POLICY (reboot or CONFIG_IMA_WRITE_POLICY=y required)"
return 1
fi
cat $IMA_POLICY > /dev/null 2>/dev/null
}
require_policy_readable()
{
if [ ! -f $IMA_POLICY ]; then
tst_brk TCONF "missing $IMA_POLICY (reboot or CONFIG_IMA_WRITE_POLICY=y required)"
fi
if ! check_policy_readable; then
tst_brk TCONF "cannot read IMA policy (CONFIG_IMA_READ_POLICY=y required)"
fi
}
require_policy_writable()
{
local err="IMA policy already loaded and kernel not configured to enable multiple writes to it (need CONFIG_IMA_WRITE_POLICY=y)"
[ -f $IMA_POLICY ] || tst_brk TCONF "$err"
# CONFIG_IMA_READ_POLICY
echo "" 2> log > $IMA_POLICY
grep -q "Device or resource busy" log && tst_brk TCONF "$err"
}
check_ima_policy_content()
{
local pattern="$1"
local grep_params="${2--q}"
check_policy_readable || return 1
grep $grep_params "$pattern" $IMA_POLICY
}
require_ima_policy_content()
{
local pattern="$1"
local grep_params="${2--q}"
require_policy_readable
if ! grep $grep_params "$pattern" $IMA_POLICY; then
tst_brk TCONF "IMA policy does not specify '$pattern'"
fi
}
check_ima_policy_cmdline()
{
local policy="$1"
local i
grep -q "ima_$policy" /proc/cmdline && return
for i in $(cat /proc/cmdline); do
if echo "$i" | grep -q '^ima_policy='; then
echo "$i" | grep -q -e "|[ ]*$policy" -e "$policy[ ]*|" -e "=$policy" && return 0
fi
done
return 1
}
require_ima_policy_cmdline()
{
local policy="$1"
check_ima_policy_cmdline $policy || \
tst_brk TCONF "IMA measurement tests require builtin IMA $policy policy (e.g. ima_policy=$policy kernel parameter)"
}
mount_helper()
{
local type="$1"
local default_dir="$2"
local dir
dir="$(grep ^$type /proc/mounts | cut -d ' ' -f2 | head -1)"
[ -n "$dir" ] && { echo "$dir"; return; }
if ! mkdir -p $default_dir; then
tst_brk TBROK "failed to create $default_dir"
fi
if ! mount -t $type $type $default_dir; then
tst_brk TBROK "failed to mount $type"
fi
UMOUNT="$default_dir $UMOUNT"
echo $default_dir
}
print_ima_config()
{
local config="${KCONFIG_PATH:-/boot/config-$(uname -r)}"
local i
if [ -r "$config" ]; then
tst_res TINFO "IMA kernel config:"
for i in $(grep ^CONFIG_IMA $config); do
tst_res TINFO "$i"
done
fi
tst_res TINFO "/proc/cmdline: $(cat /proc/cmdline)"
}
ima_setup()
{
SECURITYFS="$(mount_helper securityfs $SYSFS/kernel/security)"
IMA_DIR="$SECURITYFS/ima"
[ -d "$IMA_DIR" ] || tst_brk TCONF "IMA not enabled in kernel"
ASCII_MEASUREMENTS="$IMA_DIR/ascii_runtime_measurements"
BINARY_MEASUREMENTS="$IMA_DIR/binary_runtime_measurements"
IMA_POLICY="$IMA_DIR/policy"
# hack to support running tests locally from ima/tests directory
if [ ! -d "$TST_DATAROOT" ]; then
TST_DATAROOT="$LTPROOT/../datafiles/$TST_ID/"
fi
print_ima_config
if [ "$TST_MOUNT_DEVICE" = 1 ]; then
tst_res TINFO "\$TMPDIR is on tmpfs => run on loop device"
cd "$TST_MNTPOINT"
fi
[ -n "$TST_SETUP_CALLER" ] && $TST_SETUP_CALLER
}
ima_cleanup()
{
local dir
[ -n "$TST_CLEANUP_CALLER" ] && $TST_CLEANUP_CALLER
for dir in $UMOUNT; do
umount $dir
done
}
set_digest_index()
{
DIGEST_INDEX=
local template="$(tail -1 $ASCII_MEASUREMENTS | cut -d' ' -f 3)"
local i word
# parse digest index
# https://www.kernel.org/doc/html/latest/security/IMA-templates.html#use
case "$template" in
ima|ima-ng|ima-sig) DIGEST_INDEX=4 ;;
*)
# using ima_template_fmt kernel parameter
local IFS="|"
i=4
for word in $template; do
if [ "$word" = 'd' -o "$word" = 'd-ng' ]; then
DIGEST_INDEX=$i
break
fi
i=$((i+1))
done
esac
[ -z "$DIGEST_INDEX" ] && tst_brk TCONF \
"Cannot find digest index (template: '$template')"
}
get_algorithm_digest()
{
local line="$1"
local delimiter=':'
local algorithm digest
if [ -z "$line" ]; then
echo "measurement record not found"
return 1
fi
[ -z "$DIGEST_INDEX" ] && set_digest_index
digest=$(echo "$line" | cut -d' ' -f $DIGEST_INDEX)
if [ -z "$digest" ]; then
echo "digest not found (index: $DIGEST_INDEX, line: '$line')"
return 1
fi
if [ "${digest#*$delimiter}" != "$digest" ]; then
algorithm=$(echo "$digest" | cut -d $delimiter -f 1)
digest=$(echo "$digest" | cut -d $delimiter -f 2)
else
case "${#digest}" in
32) algorithm="md5" ;;
40) algorithm="sha1" ;;
*)
echo "algorithm must be either md5 or sha1 (digest: '$digest')"
return 1 ;;
esac
fi
if [ -z "$algorithm" ]; then
echo "algorithm not found"
return 1
fi
if [ -z "$digest" ]; then
echo "digest not found"
return 1
fi
echo "$algorithm|$digest"
}
ima_check()
{
local test_file="$1"
local algorithm digest expected_digest line tmp
# need to read file to get updated $ASCII_MEASUREMENTS
cat $test_file > /dev/null
line="$(grep $test_file $ASCII_MEASUREMENTS | tail -1)"
if tmp=$(get_algorithm_digest "$line"); then
algorithm=$(echo "$tmp" | cut -d'|' -f1)
digest=$(echo "$tmp" | cut -d'|' -f2)
else
tst_res TBROK "failed to get algorithm/digest for '$test_file': $tmp"
fi
tst_res TINFO "computing digest for $algorithm algorithm"
expected_digest="$(compute_digest $algorithm $test_file)" || \
tst_brk TCONF "cannot compute digest for $algorithm algorithm"
if [ "$digest" = "$expected_digest" ]; then
tst_res TPASS "correct digest found"
else
tst_res TFAIL "digest not found"
fi
}
# check_evmctl REQUIRED_TPM_VERSION
# return: 0: evmctl is new enough, 1: version older than required (or version < v0.9)
check_evmctl()
{
local required="$1"
local r1="$(echo $required | cut -d. -f1)"
local r2="$(echo $required | cut -d. -f2)"
local r3="$(echo $required | cut -d. -f3)"
[ -z "$r3" ] && r3=0
tst_is_int "$r1" || tst_brk TBROK "required major version not int ($v1)"
tst_is_int "$r2" || tst_brk TBROK "required minor version not int ($v2)"
tst_is_int "$r3" || tst_brk TBROK "required patch version not int ($v3)"
tst_check_cmds evmctl || return 1
local v="$(evmctl --version | cut -d' ' -f2)"
[ -z "$v" ] && return 1
tst_res TINFO "evmctl version: $v"
local v1="$(echo $v | cut -d. -f1)"
local v2="$(echo $v | cut -d. -f2)"
local v3="$(echo $v | cut -d. -f3)"
[ -z "$v3" ] && v3=0
if [ $v1 -lt $r1 ] || [ $v1 -eq $r1 -a $v2 -lt $r2 ] || \
[ $v1 -eq $r1 -a $v2 -eq $r2 -a $v3 -lt $r3 ]; then
return 1
fi
return 0
}
# require_evmctl REQUIRED_TPM_VERSION
require_evmctl()
{
local required="$1"
if ! check_evmctl $required; then
tst_brk TCONF "evmctl >= $required required"
fi
}
# loop device is needed to use only for tmpfs
TMPDIR="${TMPDIR:-/tmp}"
if tst_supported_fs -d $TMPDIR -s "tmpfs"; then
unset TST_MOUNT_DEVICE
fi
. tst_test.sh