1
0
mirror of https://git.code.sf.net/p/linux-ima/ima-evm-utils synced 2025-04-27 22:32:31 +02:00
Roberto Sassu 1d3a0b6923 Introduce expect_pass_if() and expect_fail_if()
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>
2023-03-01 11:00:22 -05:00

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
}