GLBL_ARCH_LE_SUPPORT=" \
x86 x86_64 x32 \
arm aarch64 \
mipsel mipsel64 mipsel64n32 \
ppc64le \
riscv64"
GLBL_ARCH_BE_SUPPORT=" \
mips mips64 mips64n32 \
parisc parisc64 \
ppc ppc64 \
s390 s390x"
GLBL_ARCH_32B_SUPPORT=" \
x86 x32 \
arm \
mips mipsel mips64n32 mipsel64n32 \
parisc \
ppc \
s390"
GLBL_ARCH_64B_SUPPORT=" \
x86_64 \
aarch64 \
mips64 \
parisc64 \
ppc64 \
riscv64 \
s390x"
GLBL_SYS_ARCH="../tools/scmp_arch_detect"
GLBL_SYS_RESOLVER="../tools/scmp_sys_resolver"
GLBL_SYS_SIM="../tools/scmp_bpf_sim"
GLBL_SYS_API="../tools/scmp_api_level"
function check_deps() {
[[ -z "$1" ]] && return
which "$1" >& /dev/null
return $?
}
function verify_deps() {
[[ -z "$1" ]] && return
if ! check_deps "$1"; then
echo "error: install \"$1\" and include it in your \$PATH"
exit 1
fi
}
function usage() {
cat << EOF
usage: regression [-h] [-v] [-m MODE] [-a] [-b BATCH_NAME] [-l <LOG>]
[-s SINGLE_TEST] [-t <TEMP_DIR>] [-T <TEST_TYPE>]
libseccomp regression test automation script
optional arguments:
-h show this help message and exit
-m MODE specified the test mode [c (default), python]
can also be set via LIBSECCOMP_TSTCFG_MODE_LIST env variable
-a specifies all tests are to be run
-b BATCH_NAME specifies batch of tests to be run
-l [LOG] specifies log file to write test results to
-s SINGLE_TEST specifies individual test number to be run
-t [TEMP_DIR] specifies directory to create temporary files in
-T [TEST_TYPE] only run tests matching the specified type
can also be set via LIBSECCOMP_TSTCFG_TYPE env variable
-v specifies that verbose output be provided
EOF
}
function generate_test_num() {
local testnumstr=$(printf '%s%%%%%03d-%05d' "$1" $2 $3)
echo "$testnumstr"
}
function print_data() {
if [[ -n $verbose ]]; then
printf "Test %s data: %s\n" "$1" "$2" >&$logfd
fi
}
function print_result() {
if [[ $2 == "INFO" && -z $verbose ]]; then
return
fi
if [[ $3 == "" ]]; then
printf "Test %s result: %s\n" "$1" "$2" >&$logfd
else
printf "Test %s result: %s %s\n" "$1" "$2" "$3" >&$logfd
fi
}
function print_valgrind() {
if [[ -n $verbose ]]; then
printf "Test %s valgrind output\n" "$1" >&$logfd
fi
}
function get_range() {
if [[ $2 =~ ^[0-9a-fA-Fx]+-[0-9a-fA-Fx]+$ ]]; then
range_val=$(echo "$2" | cut -d'-' -f "$1")
else
range_val="$2"
fi
echo "$range_val"
}
function get_seq() {
if [[ -x ./miniseq ]]; then
./miniseq "$1" "$2"
elif [[ -x $basedir/miniseq ]]; then
$basedir/miniseq "$1" "$2"
else
echo "error: unable to find miniseq" >&2
kill $pid
fi
}
function run_test_command() {
local cmd
if [[ $mode == "python" ]]; then
cmd="PYTHONPATH=$PYTHONPATH"
cmd="$cmd:$(cd $(pwd)/../src/python/build/lib.*; pwd)"
if [[ -e "./$2.py" ]]; then
cmd="$cmd /usr/bin/env python $2.py $3"
else
cmd="$cmd /usr/bin/env python ${srcdir}/$2.py $3"
fi
else
cmd="$2 $3"
fi
local stdout=$4
local stderr=$5
[[ -z $stdout ]] && stdout=$logfd
[[ -z $stderr ]] && stderr=$logfd
eval "$cmd" 1>&$stdout 2>&$stderr
return $?
}
function generate_random_data() {
local rcount
local rdata
if [[ $arch == "x86_64" ]]; then
rcount=$[ ($RANDOM % 16) + 1 ]
else
rcount=$[ ($RANDOM % 8) + 1 ]
fi
rdata=$(dd if=/dev/urandom bs=64 count=1 status=none | \
md5sum | awk '{ print $1 }' | head -c"$rcount")
echo "$rdata"
}
function run_test_bpf_sim_fuzz() {
local rc
local line=($3)
local testname=${line[0]}
local stress_count=${line[1]}
[[ -n $LIBSECCOMP_TSTCFG_STRESSCNT ]] && \
stress_count=$LIBSECCOMP_TSTCFG_STRESSCNT
for i in $(get_seq 1 $stress_count); do
local sys=$(generate_random_data)
local -a arg=($(generate_random_data) $(generate_random_data) \
$(generate_random_data) $(generate_random_data) \
$(generate_random_data) $(generate_random_data))
local testnumstr=$(generate_test_num "$1" $2 $i)
local -a COL_WIDTH=(26 17 17 17 17 17 17)
local testdata=$(printf "%-${COL_WIDTH[0]}s" $testname)
testdata+=$(printf "%-${COL_WIDTH[1]}s" $sys)
testdata+=$(printf "%-${COL_WIDTH[2]}s" ${arg[0]})
testdata+=$(printf "%-${COL_WIDTH[3]}s" ${arg[1]})
testdata+=$(printf "%-${COL_WIDTH[4]}s" ${arg[2]})
testdata+=$(printf "%-${COL_WIDTH[5]}s" ${arg[3]})
testdata+=$(printf "%-${COL_WIDTH[6]}s" ${arg[4]})
testdata+=$(printf "%s" ${arg[5]})
print_data "$testnumstr" "$testdata"
for i in {0..5}; do
arg[$i]=" -$i ${arg[$i]} "
done
exec 4>$tmpfile
run_test_command "$testnumstr" "./$testname" "-b" 4 ""
rc=$?
exec 4>&-
if [[ $rc -ne 0 ]]; then
print_result $testnumstr "ERROR" "$testname rc=$rc"
stats_error=$(($stats_error+1))
return
fi
allow=$($GLBL_SYS_SIM -f $tmpfile -s $sys \
${arg[0]} ${arg[1]} ${arg[2]} ${arg[3]} ${arg[4]} \
${arg[5]})
rc=$?
if [[ $rc -ne 0 ]]; then
print_result $testnumstr "ERROR" "bpf_sim rc=$rc"
stats_error=$(($stats_error+1))
else
print_result $testnumstr "SUCCESS" ""
stats_success=$(($stats_success+1))
fi
stats_all=$(($stats_all+1))
done
}
function run_test_bpf_sim() {
local rc
local LOW=1
local HIGH=2
local -a arg_empty=(false false false false false false)
local line=($3)
local testname=${line[0]}
local testarch=${line[1]}
local low_syscall local high_syscall local -a low_arg local -a high_arg local result=${line[9]}
local simarch_tmp
local simarch_avoid
simarch_tmp=""
simarch_avoid=""
for arch_i in $(echo $testarch | sed -e 's/,/ /g'); do
case $arch_i in
all)
simarch_tmp+=" $arch"
;;
all_le)
if echo "$GLBL_ARCH_LE_SUPPORT" | grep -qw "$arch"; then
simarch_tmp+=" $arch"
fi
;;
+all_le)
simarch_tmp+=" $GLBL_ARCH_LE_SUPPORT"
;;
all_be)
if echo "$GLBL_ARCH_BE_SUPPORT" | grep -qw "$arch"; then
simarch_tmp+=" $arch"
fi
;;
+all_be)
simarch_tmp+=" $GLBL_ARCH_BE_SUPPORT"
;;
all_32)
if echo "$GLBL_ARCH_32B_SUPPORT" | grep -qw "$arch"; then
simarch_tmp+=" $arch"
fi
;;
+all_32)
simarch_tmp+=" $GLBL_ARCH_32B_SUPPORT"
;;
all_64)
if echo "$GLBL_ARCH_64B_SUPPORT" | grep -qw "$arch"; then
simarch_tmp+=" $arch"
fi
;;
+all_64)
simarch_tmp+=" $GLBL_ARCH_64B_SUPPORT"
;;
+*)
simarch_tmp+=" ${arch_i:1}"
;;
-*)
simarch_avoid+=" ${arch_i:1}"
;;
*)
if [[ "$arch_i" == "$arch" ]]; then
simarch_tmp+=" $arch_i"
fi
;;
esac
done
local simarch_list
simarch_list=""
for arch_i in $simarch_tmp; do
if echo "$simarch_avoid" | grep -q -v -w "$arch_i"; then
simarch_list+=" $arch_i"
fi
done
simarch_list=$(echo $simarch_list | sed -e 's/ / /g;s/^ //;')
if [[ $simarch_list == "" ]]; then
print_result $(generate_test_num "$1" $2 1) "SKIPPED" \
"(architecture difference)"
stats_skipped=$(($stats_skipped+1))
return
fi
line_i=3
for arg_i in {0..5}; do
low_arg[$arg_i]=$(get_range $LOW "${line[$line_i]}")
high_arg[$arg_i]=$(get_range $HIGH "${line[$line_i]}")
if [[ ${low_arg[$arg_i]} == "N" ]]; then
arg_empty[$arg_i]=true
low_arg[$arg_i]=0
high_arg[$arg_i]=0
fi
line_i=$(($line_i+1))
done
for simarch in $simarch_list; do
if [[ $simarch != $simarch_list ]]; then
echo " test arch: $simarch" >&$logfd
fi
local subtestnum=1
low_syscall=$(get_range $LOW "${line[2]}")
if [[ ! $low_syscall =~ ^\-?[0-9]+$ ]]; then
low_syscall=$($GLBL_SYS_RESOLVER -a $simarch -t \
$low_syscall)
if [[ $? -ne 0 ]]; then
print_result $(generate_test_num "$1" $2 1) \
"ERROR" "sys_resolver rc=$?"
stats_error=$(($stats_error+1))
return
fi
fi
high_syscall=$(get_range $HIGH "${line[2]}")
if [[ ! $high_syscall =~ ^\-?[0-9]+$ ]]; then
high_syscall=$($GLBL_SYS_RESOLVER -a $simarch -t \
$high_syscall)
if [[ $? -ne 0 ]]; then
print_result $(generate_test_num "$1" $2 1) \
"ERROR" "sys_resolver rc=$?"
stats_error=$(($stats_error+1))
return
fi
fi
for sys in $(get_seq $low_syscall $high_syscall); do
for arg0 in $(get_seq ${low_arg[0]} ${high_arg[0]}); do
for arg1 in $(get_seq ${low_arg[1]} ${high_arg[1]}); do
for arg2 in $(get_seq ${low_arg[2]} ${high_arg[2]}); do
for arg3 in $(get_seq ${low_arg[3]} ${high_arg[3]}); do
for arg4 in $(get_seq ${low_arg[4]} ${high_arg[4]}); do
for arg5 in $(get_seq ${low_arg[5]} ${high_arg[5]}); do
local -a arg=($arg0 $arg1 $arg2 $arg3 $arg4 $arg5)
local testnumstr=$(generate_test_num "$1" $2 \
$subtestnum)
for i in {0..5}; do
if ${arg_empty[$i]}; then
arg[$i]="N"
fi
done
local -a COL_WIDTH=(26 08 14 11 17 21 09 06 06)
local testdata=$(printf "%-${COL_WIDTH[0]}s" $testname)
testdata+=$(printf "%-${COL_WIDTH[1]}s" $simarch)
testdata+=$(printf "%-${COL_WIDTH[2]}s" $sys)
testdata+=$(printf "%-${COL_WIDTH[3]}s" ${arg[0]})
testdata+=$(printf "%-${COL_WIDTH[4]}s" ${arg[1]})
testdata+=$(printf "%-${COL_WIDTH[5]}s" ${arg[2]})
testdata+=$(printf "%-${COL_WIDTH[6]}s" ${arg[3]})
testdata+=$(printf "%-${COL_WIDTH[7]}s" ${arg[4]})
testdata+=$(printf "%-${COL_WIDTH[8]}s" ${arg[5]})
testdata+=$(printf "%-${COL_WIDTH[9]}s" $result)
print_data "$testnumstr" "$testdata"
for i in {0..5}; do
if ${arg_empty[$i]}; then
arg[$i]=""
else
arg[$i]=" -$i ${arg[$i]} "
fi
done
exec 4>$tmpfile
run_test_command "$testnumstr" "./$testname" "-b" 4 ""
rc=$?
exec 4>&-
if [[ $rc -ne 0 ]]; then
print_result $testnumstr \
"ERROR" "$testname rc=$rc"
stats_error=$(($stats_error+1))
return
fi
action=$($GLBL_SYS_SIM -a $simarch -f $tmpfile \
-s $sys ${arg[0]} ${arg[1]} ${arg[2]} \
${arg[3]} ${arg[4]} ${arg[5]})
rc=$?
if [[ $rc -ne 0 ]]; then
print_result $testnumstr \
"ERROR" "bpf_sim rc=$rc"
stats_error=$(($stats_error+1))
elif [[ "$action" != "$result" ]]; then
print_result $testnumstr "FAILURE" \
"bpf_sim resulted in $action"
stats_failure=$(($stats_failure+1))
else
print_result $testnumstr "SUCCESS" ""
stats_success=$(($stats_success+1))
fi
stats_all=$(($stats_all+1))
subtestnum=$(($subtestnum+1))
done done done done done done done done }
function run_test_basic() {
local rc
local cmd
if [[ $mode != "c" && "$2" == *.sh ]]; then
print_result "$1" "SKIPPED" "(only valid in native/c mode)"
stats_skipped=$(($stats_skipped+1))
return
fi
print_data "$1" "$2"
if [[ -x "./$2" ]]; then
cmd="./$2"
else
cmd="${srcdir}/$2"
fi
run_test_command "$1" "$cmd" "" "" ""
rc=$?
if [[ $rc -ne 0 ]]; then
print_result $1 "FAILURE" "$2 rc=$rc"
stats_failure=$(($stats_failure+1))
else
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
fi
stats_all=$(($stats_all+1))
}
function run_test_bpf_valgrind() {
local rc
if [[ $mode != "c" ]]; then
print_result "$1" "SKIPPED" "(only valid in native/c mode)"
stats_skipped=$(($stats_skipped+1))
return
fi
print_data "$1" "$2"
testvalgrind="valgrind \
--tool=memcheck \
--error-exitcode=1 \
--leak-check=full \
--read-var-info=yes \
--track-origins=yes \
--suppressions=$basedir/valgrind_test.supp"
if [[ -n $logfile ]]; then
testvalgrind+=" --log-fd=$logfd"
fi
if [[ -z $verbose ]]; then
testvalgrind+=" --quiet --log-fd=4"
fi
exec 4>/dev/null
print_valgrind "$1"
run_test_command "$1" "$testvalgrind --" "./$2 -b" 4 2
rc=$?
exec 4>&-
if [[ $rc -ne 0 ]]; then
print_result $1 "FAILURE" "$2 rc=$rc"
stats_failure=$(($stats_failure+1))
else
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
fi
stats_all=$(($stats_all+1))
}
function run_test_live() {
local rc
local api
local line=($2)
line_cmd=${line[0]}
line_api=${line[1]}
line_act=${line[2]}
line_test="$line_cmd $line_api $line_act"
api=$($GLBL_SYS_API)
if [[ $api -lt $line_api ]]; then
print_result "$1" "SKIPPED" "(api level)"
stats_skipped=$(($stats_skipped+1))
return
fi
print_data "$1" "$2"
exec 4>/dev/null
run_test_command "$1" "./$line_cmd" "$line_act" "" 4
rc=$?
exec 4>&-
stats_all=$(($stats_all+1))
case "$arch" in
x86|x86_64|x32|arm|aarch64|parisc|parisc64|ppc|ppc64|ppc64le|ppc|s390|s390x|riscv64)
rc_kill_process=159
rc_kill=159
rc_allow=160
rc_trap=161
rc_trace=162
rc_errno=163
rc_log=164
;;
mips|mipsel|mips64|mips64n32|mipsel64|mipsel64n32)
rc_kill_process=140
rc_kill=140
rc_allow=160
rc_trap=161
rc_trace=162
rc_errno=163
rc_log=164
;;
*)
print_result $testnumstr "ERROR" "arch $arch not supported"
stats_error=$(($stats_error+1))
return
;;
esac
if [[ $line_act == "KILL_PROCESS" && $rc -eq $rc_kill_process ]]; then
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
elif [[ $line_act == "KILL" && $rc -eq $rc_kill ]]; then
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
elif [[ $line_act == "ALLOW" && $rc -eq $rc_allow ]]; then
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
elif [[ $line_act == "TRAP" && $rc -eq $rc_trap ]]; then
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
elif [[ $line_act == "TRACE" ]]; then
print_result $1 "ERROR" "unsupported action \"$line_act\""
stats_error=$(($stats_error+1))
elif [[ $line_act == "ERRNO" && $rc -eq $rc_errno ]]; then
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
elif [[ $line_act == "LOG" && $rc -eq $rc_log ]]; then
print_result $1 "SUCCESS" ""
stats_success=$(($stats_success+1))
else
print_result $1 "FAILURE" "$line_test rc=$rc"
stats_failure=$(($stats_failure+1))
fi
}
function run_test() {
local testnumstr=$(generate_test_num "$1" $2 1)
[[ -n $type && "$4" != "$type" ]] && return
if [[ "$4" == "basic" ]]; then
run_test_basic "$testnumstr" "$3"
elif [[ "$4" == "bpf-sim" ]]; then
run_test_bpf_sim "$1" $2 "$3"
elif [[ "$4" == "bpf-sim-fuzz" ]]; then
run_test_bpf_sim_fuzz "$1" $2 "$3"
elif [[ "$4" == "bpf-valgrind" ]]; then
if check_deps valgrind; then
run_test_bpf_valgrind "$testnumstr" "$3"
else
print_result $testnumstr "SKIPPED" \
"(valgrind not installed)"
stats_skipped=$(($stats_skipped+1))
fi
elif [[ "$4" == "live" ]]; then
if [[ -n $type ]]; then
run_test_live "$testnumstr" "$3"
else
print_result $testnumstr "SKIPPED" \
"(must specify live tests)"
stats_skipped=$(($stats_skipped+1))
fi
else
print_result $testnumstr "ERROR" "test type $4 not supported"
stats_error=$(($stats_error+1))
fi
}
function run_tests() {
for file in $basedir/*.tests; do
local testnum=1
local batch_requested=false
local batch_name=""
batch_name=$(basename $file .tests)
if [[ ${batch_list[@]} ]]; then
for b in ${batch_list[@]}; do
if [[ $b == $batch_name ]]; then
batch_requested=true
break
fi
done
if ! $batch_requested; then
continue
fi
fi
echo " batch name: $batch_name" >&$logfd
while read line; do
line=$(echo "$line" | \
sed -e 's/^[\t ]*//;s/[\t ]*$//;' | \
sed -e '/^[#].*$/d;/^$/d')
if [[ -z $line ]]; then
continue
fi
if [[ $line =~ ^"test type": ]]; then
test_type=$(echo "$line" | \
sed -e 's/^test type: //;')
echo " test mode: $mode" >&$logfd
echo " test type: $test_type" >&$logfd
continue
fi
if [[ ${single_list[@]} ]]; then
for i in ${single_list[@]}; do
if [ $i -eq $testnum ]; then
run_test "$batch_name" \
$testnum "$line" \
"$test_type"
fi
done
else
run_test "$batch_name" \
$testnum "$line" "$test_type"
fi
testnum=$(($testnum+1))
done < "$file"
done
}
verify_deps head
verify_deps sed
verify_deps awk
verify_deps tr
declare -a batch_list
declare -a single_list
arch=
batch_count=0
logfile=
logfd=
mode_list=""
runall=
singlecount=0
tmpfile=""
tmpdir=""
type=
verbose=
stats_all=0
stats_skipped=0
stats_success=0
stats_failure=0
stats_error=0
basedir=$(dirname $0)
pid=$$
while getopts "ab:gl:m:s:t:T:vh" opt; do
case $opt in
a)
runall=1
;;
b)
batch_list[batch_count]="$OPTARG"
batch_count=$(($batch_count+1))
;;
l)
logfile="$OPTARG"
;;
m)
case $OPTARG in
c)
mode_list="$mode_list c"
;;
python)
verify_deps python
mode_list="$mode_list python"
;;
*)
usage
exit 1
esac
;;
s)
single_list[single_count]=$OPTARG
single_count=$(($single_count+1))
;;
t)
tmpdir="$OPTARG"
;;
T)
type="$OPTARG"
;;
v)
verbose=1
;;
h|*)
usage
exit 1
;;
esac
done
[[ -z $mode_list && -n $LIBSECCOMP_TSTCFG_MODE_LIST ]] && mode_list=$LIBSECCOMP_TSTCFG_MODE_LIST
if [[ -z $mode_list ]]; then
mode_list="c"
if [[ -r "../configure.h" ]]; then
[[ "$(grep "ENABLE_PYTHON" ../configure.h | \
awk '{ print $3 }')" = "1" ]] && \
mode_list="$mode_list python"
fi
fi
if [[ -z $batch_list ]] && [[ -z $single_list ]]; then
runall=1
fi
if [[ -n $runall ]]; then
batch_list=()
single_list=()
fi
[[ -z $type && -n $LIBSECCOMP_TSTCFG_TYPE ]] && type=$LIBSECCOMP_TSTCFG_TYPE
if [[ -n $logfile ]]; then
logfd=3
exec 3>>"$logfile"
else
logfd=1
fi
if [[ -n $tmpdir ]]; then
tmpfile=$(mktemp -t regression_XXXXXX --tmpdir=$tmpdir)
else
tmpfile=$(mktemp -t regression_XXXXXX)
fi
arch=$($GLBL_SYS_ARCH)
echo "=============== $(date) ===============" >&$logfd
echo "Regression Test Report (\"regression $*\")" >&$logfd
for mode in $mode_list; do
run_tests
done
echo "Regression Test Summary" >&$logfd
echo " tests run: $stats_all" >&$logfd
echo " tests skipped: $stats_skipped" >&$logfd
echo " tests passed: $stats_success" >&$logfd
echo " tests failed: $stats_failure" >&$logfd
echo " tests errored: $stats_error" >&$logfd
echo "============================================================" >&$logfd
rm -f $tmpfile
rc=0
[[ $stats_failure -gt 0 ]] && rc=$(($rc + 2))
[[ $stats_error -gt 0 ]] && rc=$(($rc + 4))
exit $rc