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 f106a9022d Add support for creating a new testing environment in functions.sh
Add the new functions _run_env(), _exit_env(), _init_env() and
_cleanup_env() to run the tests inside a new environment specified with the
TST_ENV environment variable.

A typical structure of a script with tests is:

trap '_report_exit_and_cleanup _cleanup_env cleanup' \
    SIGINT SIGTERM SIGSEGV EXIT

cleanup() {
	<test cleanup>
}

<tests implementations>

_run_env "$TST_KERNEL" "$PWD/$(basename "$0")" "env_var1=$env_var1 ..."

_exit_env "$TST_KERNEL"

_init_env

<tests init>

<tests call>

If TST_ENV is not set or empty, don't create a new testing environment and
perform the cleanup in the current environment. Don't create a new testing
environment also if the script is already executed in a new environment, to
avoid loops. Instead, for cleanup, do it in the new environment and skip it
in the host environment (if the cleanup function is passed to
_cleanup_env()).

Signal to the creator of the environment failures of tests or of the script
itself run in the new environment (if the exit code is 1 ($FAIL) or 99
($HARDFAIL)) with an unclean shutdown of the system.

Add haveged and systemd as dependencies for the tests in ci/fedora.sh,
respectively for initializing the random number generator and for shutting
down the system in the new environment.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
2023-01-27 11:40:16 -05:00

413 lines
9.7 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 [ $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
}
# Eval negative test (one that should fail) and account its result
expect_fail() {
local ret
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
}
# 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
}