#!/bin/sh # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (c) 2009 IBM Corporation # Copyright (c) 2018-2020 Petr Vorel # Author: Mimi Zohar 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_NEEDS_DEVICE can be unset, therefore specify explicitly TST_NEEDS_TMPDIR=1 . tst_test.sh 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 } mount_loop_device() { local ret tst_mkfs tst_mount cd $TST_MNTPOINT } 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_NEEDS_DEVICE" = 1 ]; then tst_res TINFO "\$TMPDIR is on tmpfs => run on loop device" mount_loop_device 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 if [ "$TST_NEEDS_DEVICE" = 1 ]; then cd $TST_TMPDIR tst_umount fi } 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 [ "$(df -T $TMPDIR | tail -1 | awk '{print $2}')" != "tmpfs" -a -n "$TST_NEEDS_DEVICE" ]; then unset TST_NEEDS_DEVICE fi