mirror of
https://git.code.sf.net/p/linux-ima/ima-evm-utils
synced 2025-04-27 06:12:32 +02:00

Introduce these functions to let the developer specify which kernel patches are required for the tests to be successful (either pass or fail). If a test is not successful, print those patches in the test result summary. First, the developer should declare an array, named PATCHES, with the list of all kernel patches that are required by the tests. For example: PATCHES=( 'patch 1 title' ... 'patch N title' ) Second, the developer could replace the existing expect_pass() and expect_fail() respectively with expect_pass_if() and expect_fail_if(), and add the indexes in the PATCHES array as the first argument, enclosed with quotes. The other parameters of expect_pass() and expect_fail() remain the same. In the following example, the PATCHES array has been added to a new test script, tests/mmap_check.test: PATCHES=( 'ima: Align ima_file_mmap() parameters with mmap_file LSM hook' 'ima: Introduce MMAP_CHECK_REQPROT hook' ) Then, expect_pass() has been replaced with expect_pass_if(): expect_pass_if '0' check_mmap "MMAP_CHECK" "read_implies_exec" The resulting output when a test fails (if the required patch is not applied) is: Test: check_mmap (hook="MMAP_CHECK", test_mmap arg: "read_implies_exec") Result (expect found): not found Possibly missing patches: - ima: Align ima_file_mmap() parameters with mmap_file LSM hook Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com> Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
463 lines
11 KiB
Bash
Executable File
463 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
#
|
|
# ima-evm-utils tests bash functions
|
|
#
|
|
# Copyright (C) 2020 Vitaly Chikunov <vt@altlinux.org>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2, or (at your option)
|
|
# any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
|
|
# Tests accounting
|
|
declare -i testspass=0 testsfail=0 testsskip=0
|
|
|
|
# Exit codes (compatible with automake)
|
|
declare -r OK=0
|
|
declare -r FAIL=1
|
|
declare -r HARDFAIL=99 # hard failure no matter testing mode
|
|
declare -r SKIP=77
|
|
|
|
# You can set env VERBOSE=1 to see more output from evmctl
|
|
VERBOSE=${VERBOSE:-0}
|
|
V=vvvv
|
|
V=${V:0:$VERBOSE}
|
|
V=${V:+-$V}
|
|
|
|
# Exit if env FAILEARLY is defined.
|
|
# Used in expect_{pass,fail}.
|
|
exit_early() {
|
|
if [ "$FAILEARLY" ]; then
|
|
exit "$1"
|
|
fi
|
|
}
|
|
|
|
# Require particular executables to be present
|
|
_require() {
|
|
ret=
|
|
for i; do
|
|
if ! type $i; then
|
|
echo "$i is required for test"
|
|
ret=1
|
|
fi
|
|
done
|
|
[ $ret ] && exit "$HARDFAIL"
|
|
}
|
|
|
|
# Non-TTY output is never colored
|
|
if [ -t 1 ]; then
|
|
RED=$'\e[1;31m'
|
|
GREEN=$'\e[1;32m'
|
|
YELLOW=$'\e[1;33m'
|
|
BLUE=$'\e[1;34m'
|
|
CYAN=$'\e[1;36m'
|
|
NORM=$'\e[m'
|
|
export RED GREEN YELLOW BLUE CYAN NORM
|
|
fi
|
|
|
|
# Test mode determined by TFAIL variable:
|
|
# undefined: to success testing
|
|
# defined: failure testing
|
|
TFAIL=
|
|
TMODE=+ # mode character to prepend running command in log
|
|
declare -i TNESTED=0 # just for sanity checking
|
|
|
|
# Run positive test (one that should pass) and account its result
|
|
expect_pass() {
|
|
local -i ret
|
|
|
|
if [ -n "$TST_LIST" ] && [ "${TST_LIST/$1/}" = "$TST_LIST" ]; then
|
|
[ "$VERBOSE" -gt 1 ] && echo "____ SKIP test: $*"
|
|
testsskip+=1
|
|
return "$SKIP"
|
|
fi
|
|
|
|
if [ $TNESTED -gt 0 ]; then
|
|
echo $RED"expect_pass should not be run nested"$NORM
|
|
testsfail+=1
|
|
exit "$HARDFAIL"
|
|
fi
|
|
TFAIL=
|
|
TMODE=+
|
|
TNESTED+=1
|
|
[ "$VERBOSE" -gt 1 ] && echo "____ START positive test: $*"
|
|
"$@"
|
|
ret=$?
|
|
[ "$VERBOSE" -gt 1 ] && echo "^^^^ STOP ($ret) positive test: $*"
|
|
TNESTED+=-1
|
|
case $ret in
|
|
0) testspass+=1 ;;
|
|
77) testsskip+=1 ;;
|
|
99) testsfail+=1; exit_early 1 ;;
|
|
*) testsfail+=1; exit_early 2 ;;
|
|
esac
|
|
return $ret
|
|
}
|
|
|
|
expect_pass_if() {
|
|
local indexes="$1"
|
|
local ret idx
|
|
|
|
shift
|
|
|
|
expect_pass "$@"
|
|
ret=$?
|
|
|
|
if [ $ret -ne 0 ] && [ $ret -ne 77 ] && [ -n "$PATCHES" ]; then
|
|
echo $YELLOW"Possibly missing patches:"$NORM
|
|
for idx in $indexes; do
|
|
echo $YELLOW" - ${PATCHES[$((idx))]}"$NORM
|
|
done
|
|
fi
|
|
|
|
return $ret
|
|
}
|
|
|
|
# Eval negative test (one that should fail) and account its result
|
|
expect_fail() {
|
|
local ret
|
|
|
|
if [ -n "$TST_LIST" ] && [ "${TST_LIST/$1/}" = "$TST_LIST" ]; then
|
|
[ "$VERBOSE" -gt 1 ] && echo "____ SKIP test: $*"
|
|
testsskip+=1
|
|
return "$SKIP"
|
|
fi
|
|
|
|
if [ $TNESTED -gt 0 ]; then
|
|
echo $RED"expect_fail should not be run nested"$NORM
|
|
testsfail+=1
|
|
exit "$HARDFAIL"
|
|
fi
|
|
|
|
TFAIL=yes
|
|
TMODE=-
|
|
TNESTED+=1
|
|
[ "$VERBOSE" -gt 1 ] && echo "____ START negative test: $*"
|
|
"$@"
|
|
ret=$?
|
|
[ "$VERBOSE" -gt 1 ] && echo "^^^^ STOP ($ret) negative test: $*"
|
|
TNESTED+=-1
|
|
case $ret in
|
|
0) testsfail+=1; exit_early 3 ;;
|
|
77) testsskip+=1 ;;
|
|
99) testsfail+=1; exit_early 4 ;;
|
|
*) testspass+=1 ;;
|
|
esac
|
|
# Restore defaults (as in positive tests)
|
|
# for tests to run without wrappers
|
|
TFAIL=
|
|
TMODE=+
|
|
return $ret
|
|
}
|
|
|
|
expect_fail_if() {
|
|
local indexes="$1"
|
|
local ret idx
|
|
|
|
shift
|
|
|
|
expect_fail "$@"
|
|
ret=$?
|
|
|
|
if { [ $ret -eq 0 ] || [ $ret -eq 99 ]; } && [ -n "$PATCHES" ]; then
|
|
echo $YELLOW"Possibly missing patches:"$NORM
|
|
for idx in $indexes; do
|
|
echo $YELLOW" - ${PATCHES[$((idx))]}"$NORM
|
|
done
|
|
fi
|
|
|
|
return $ret
|
|
}
|
|
|
|
# return true if current test is positive
|
|
_test_expected_to_pass() {
|
|
[ ! $TFAIL ]
|
|
}
|
|
|
|
# return true if current test is negative
|
|
_test_expected_to_fail() {
|
|
[ $TFAIL ]
|
|
}
|
|
|
|
# Show blank line and color following text to red
|
|
# if it's real error (ie we are in expect_pass mode).
|
|
color_red_on_failure() {
|
|
if _test_expected_to_pass; then
|
|
echo "$RED"
|
|
COLOR_RESTORE=true
|
|
fi
|
|
}
|
|
|
|
# For hard errors
|
|
color_red() {
|
|
echo "$RED"
|
|
COLOR_RESTORE=true
|
|
}
|
|
|
|
color_restore() {
|
|
[ $COLOR_RESTORE ] && echo "$NORM"
|
|
COLOR_RESTORE=
|
|
}
|
|
|
|
ADD_DEL=
|
|
ADD_TEXT_FOR=
|
|
# _evmctl_run should be run as `_evmctl_run ... || return'
|
|
_evmctl_run() {
|
|
local op=$1 out=$1-$$.out
|
|
local text_for=${FOR:+for $ADD_TEXT_FOR}
|
|
# Additional parameters:
|
|
# ADD_DEL: additional files to rm on failure
|
|
# ADD_TEXT_FOR: append to text as 'for $ADD_TEXT_FOR'
|
|
|
|
cmd="evmctl $V $EVMCTL_ENGINE $*"
|
|
echo $YELLOW$TMODE "$cmd"$NORM
|
|
$cmd >"$out" 2>&1
|
|
ret=$?
|
|
|
|
# Shell special and signal exit codes (except 255)
|
|
if [ $ret -ge 126 ] && [ $ret -lt 255 ]; then
|
|
color_red
|
|
echo "evmctl $op failed hard with ($ret) $text_for"
|
|
sed 's/^/ /' "$out"
|
|
color_restore
|
|
rm "$out" $ADD_DEL
|
|
ADD_DEL=
|
|
ADD_TEXT_FOR=
|
|
return "$HARDFAIL"
|
|
elif [ $ret -gt 0 ]; then
|
|
color_red_on_failure
|
|
echo "evmctl $op failed" ${TFAIL:+properly} "with ($ret) $text_for"
|
|
# Show evmctl output only in verbose mode or if real failure.
|
|
if _test_expected_to_pass || [ "$VERBOSE" ]; then
|
|
sed 's/^/ /' "$out"
|
|
fi
|
|
color_restore
|
|
rm "$out" $ADD_DEL
|
|
ADD_DEL=
|
|
ADD_TEXT_FOR=
|
|
return "$FAIL"
|
|
elif _test_expected_to_fail; then
|
|
color_red
|
|
echo "evmctl $op wrongly succeeded $text_for"
|
|
sed 's/^/ /' "$out"
|
|
color_restore
|
|
else
|
|
[ "$VERBOSE" ] && sed 's/^/ /' "$out"
|
|
fi
|
|
rm "$out"
|
|
ADD_DEL=
|
|
ADD_TEXT_FOR=
|
|
return "$OK"
|
|
}
|
|
|
|
# Extract xattr $attr from $file into $out file skipping $pref'ix
|
|
_extract_xattr() {
|
|
local file=$1 attr=$2 out=$3 pref=$4
|
|
|
|
getfattr -n "$attr" -e hex "$file" \
|
|
| grep "^$attr=" \
|
|
| sed "s/^$attr=$pref//" \
|
|
| xxd -r -p > "$out"
|
|
}
|
|
|
|
# Test if xattr $attr in $file matches $prefix
|
|
# Show error and fail otherwise.
|
|
_test_xattr() {
|
|
local file=$1 attr=$2 prefix=$3
|
|
local text_for=${ADD_TEXT_FOR:+ for $ADD_TEXT_FOR}
|
|
|
|
if ! getfattr -n "$attr" -e hex "$file" | egrep -qx "$attr=$prefix"; then
|
|
color_red_on_failure
|
|
echo "Did not find expected hash$text_for:"
|
|
echo " $attr=$prefix"
|
|
echo ""
|
|
echo "Actual output below:"
|
|
getfattr -n "$attr" -e hex "$file" | sed 's/^/ /'
|
|
color_restore
|
|
rm "$file"
|
|
ADD_TEXT_FOR=
|
|
return "$FAIL"
|
|
fi
|
|
ADD_TEXT_FOR=
|
|
}
|
|
|
|
# Try to enable gost-engine if needed.
|
|
_enable_gost_engine() {
|
|
# Do not enable if it's already working (enabled by user)
|
|
if ! openssl md_gost12_256 /dev/null >/dev/null 2>&1 \
|
|
&& openssl engine gost >/dev/null 2>&1; then
|
|
export EVMCTL_ENGINE="--engine gost"
|
|
export OPENSSL_ENGINE="-engine gost"
|
|
fi
|
|
}
|
|
|
|
# Show test stats and exit into automake test system
|
|
# with proper exit code (same as ours). Do cleanups.
|
|
_report_exit_and_cleanup() {
|
|
local exit_code=$?
|
|
|
|
if [ -n "${WORKDIR}" ]; then
|
|
rm -rf "${WORKDIR}"
|
|
fi
|
|
|
|
"$@"
|
|
|
|
if [ $testsfail -gt 0 ]; then
|
|
echo "================================="
|
|
echo " Run with FAILEARLY=1 $0 $*"
|
|
echo " To stop after first failure"
|
|
echo "================================="
|
|
fi
|
|
[ $testspass -gt 0 ] && echo -n "$GREEN" || echo -n "$NORM"
|
|
echo -n "PASS: $testspass"
|
|
[ $testsskip -gt 0 ] && echo -n "$YELLOW" || echo -n "$NORM"
|
|
echo -n " SKIP: $testsskip"
|
|
[ $testsfail -gt 0 ] && echo -n "$RED" || echo -n "$NORM"
|
|
echo " FAIL: $testsfail"
|
|
echo "$NORM"
|
|
# Signal failure to the testing environment creator with an unclean shutdown.
|
|
if [ -n "$TST_ENV" ] && [ $$ -eq 1 ]; then
|
|
if [ -z "$(command -v poweroff)" ]; then
|
|
echo "Warning: cannot properly shutdown system"
|
|
fi
|
|
|
|
# If no test was executed and the script was successful,
|
|
# do a clean shutdown.
|
|
if [ $testsfail -eq 0 ] && [ $testspass -eq 0 ] && [ $testsskip -eq 0 ] &&
|
|
[ $exit_code -ne "$FAIL" ] && [ $exit_code -ne "$HARDFAIL" ]; then
|
|
poweroff -f
|
|
fi
|
|
|
|
# If tests were executed and no test failed, do a clean shutdown.
|
|
if { [ $testspass -gt 0 ] || [ $testsskip -gt 0 ]; } &&
|
|
[ $testsfail -eq 0 ]; then
|
|
poweroff -f
|
|
fi
|
|
fi
|
|
if [ $testsfail -gt 0 ]; then
|
|
exit "$FAIL"
|
|
elif [ $testspass -gt 0 ]; then
|
|
exit "$OK"
|
|
elif [ $testsskip -gt 0 ]; then
|
|
exit "$SKIP"
|
|
else
|
|
exit "$exit_code"
|
|
fi
|
|
}
|
|
|
|
# Setup SoftHSM for local testing by calling the softhsm_setup script.
|
|
# Use the provided workdir as the directory where SoftHSM will store its state
|
|
# into.
|
|
# Upon successfully setting up SoftHSM, this function sets the global variables
|
|
# OPENSSL_ENGINE and OPENSSL_KEYFORM so that the openssl command line tool can
|
|
# use SoftHSM. Also the PKCS11_KEYURI global variable is set to the test key's
|
|
# pkcs11 URI.
|
|
_softhsm_setup() {
|
|
local workdir="$1"
|
|
|
|
local msg
|
|
|
|
export SOFTHSM_SETUP_CONFIGDIR="${workdir}/softhsm"
|
|
export SOFTHSM2_CONF="${workdir}/softhsm/softhsm2.conf"
|
|
|
|
mkdir -p "${SOFTHSM_SETUP_CONFIGDIR}"
|
|
|
|
msg=$(./softhsm_setup setup 2>&1)
|
|
if [ $? -eq 0 ]; then
|
|
echo "softhsm_setup setup succeeded: $msg"
|
|
PKCS11_KEYURI=$(echo $msg | sed -n 's|^keyuri: \(.*\)|\1|p')
|
|
|
|
export EVMCTL_ENGINE="--engine pkcs11"
|
|
export OPENSSL_ENGINE="-engine pkcs11"
|
|
export OPENSSL_KEYFORM="-keyform engine"
|
|
else
|
|
echo "softhsm_setup setup failed: ${msg}"
|
|
fi
|
|
}
|
|
|
|
# Tear down the SoftHSM setup and clean up the environment
|
|
_softhsm_teardown() {
|
|
./softhsm_setup teardown &>/dev/null
|
|
rm -rf "${SOFTHSM_SETUP_CONFIGDIR}"
|
|
unset SOFTHSM_SETUP_CONFIGDIR SOFTHSM2_CONF PKCS11_KEYURI \
|
|
EVMCTL_ENGINE OPENSSL_ENGINE OPENSSL_KEYFORM
|
|
}
|
|
|
|
# Syntax: _run_env <kernel> <init> <additional kernel parameters>
|
|
_run_env() {
|
|
if [ -z "$TST_ENV" ]; then
|
|
return
|
|
fi
|
|
|
|
if [ $$ -eq 1 ]; then
|
|
return
|
|
fi
|
|
|
|
if [ "$TST_ENV" = "um" ]; then
|
|
expect_pass "$1" rootfstype=hostfs rw init="$2" quiet mem=2048M "$3"
|
|
else
|
|
echo $RED"Testing environment $TST_ENV not supported"$NORM
|
|
exit "$FAIL"
|
|
fi
|
|
}
|
|
|
|
# Syntax: _exit_env <kernel>
|
|
_exit_env() {
|
|
if [ -z "$TST_ENV" ]; then
|
|
return
|
|
fi
|
|
|
|
if [ $$ -eq 1 ]; then
|
|
return
|
|
fi
|
|
|
|
exit "$OK"
|
|
}
|
|
|
|
# Syntax: _init_env
|
|
_init_env() {
|
|
if [ -z "$TST_ENV" ]; then
|
|
return
|
|
fi
|
|
|
|
if [ $$ -ne 1 ]; then
|
|
return
|
|
fi
|
|
|
|
mount -t tmpfs tmpfs /tmp
|
|
mount -t proc proc /proc
|
|
mount -t sysfs sysfs /sys
|
|
mount -t securityfs securityfs /sys/kernel/security
|
|
|
|
if [ -n "$(command -v haveged 2> /dev/null)" ]; then
|
|
$(command -v haveged) -w 1024 &> /dev/null
|
|
fi
|
|
|
|
pushd "$PWD" > /dev/null || exit "$FAIL"
|
|
}
|
|
|
|
# Syntax: _cleanup_env <cleanup function>
|
|
_cleanup_env() {
|
|
if [ -z "$TST_ENV" ]; then
|
|
$1
|
|
return
|
|
fi
|
|
|
|
if [ $$ -ne 1 ]; then
|
|
return
|
|
fi
|
|
|
|
$1
|
|
|
|
umount /sys/kernel/security
|
|
umount /sys
|
|
umount /proc
|
|
umount /tmp
|
|
}
|