679 lines · bash
1#!/bin/bash2# SPDX-License-Identifier: GPL-2.0-or-later OR copyleft-next-0.3.13# Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>4#5# This is a stress test script for kmod, the kernel module loader. It uses6# test_kmod which exposes a series of knobs for the API for us so we can7# tweak each test in userspace rather than in kernelspace.8#9# The way kmod works is it uses the kernel's usermode helper API to eventually10# call /sbin/modprobe. It has a limit of the number of concurrent calls11# possible. The kernel interface to load modules is request_module(), however12# mount uses get_fs_type(). Both behave slightly differently, but the13# differences are important enough to test each call separately. For this14# reason test_kmod starts by providing tests for both calls.15#16# The test driver test_kmod assumes a series of defaults which you can17# override by exporting to your environment prior running this script.18# For instance this script assumes you do not have xfs loaded upon boot.19# If this is false, export DEFAULT_KMOD_FS="ext4" prior to running this20# script if the filesystem module you don't have loaded upon bootup21# is ext4 instead. Refer to allow_user_defaults() for a list of user22# override variables possible.23#24# You'll want at least 4 GiB of RAM to expect to run these tests25# without running out of memory on them. For other requirements refer26# to test_reqs()27 28set -e29 30TEST_NAME="kmod"31TEST_DRIVER="test_${TEST_NAME}"32TEST_DIR=$(dirname $0)33 34# This represents35#36# TEST_ID:TEST_COUNT:ENABLED37#38# TEST_ID: is the test id number39# TEST_COUNT: number of times we should run the test40# ENABLED: 1 if enabled, 0 otherwise41#42# Once these are enabled please leave them as-is. Write your own test,43# we have tons of space.44ALL_TESTS="0001:3:1"45ALL_TESTS="$ALL_TESTS 0002:3:1"46ALL_TESTS="$ALL_TESTS 0003:1:1"47ALL_TESTS="$ALL_TESTS 0004:1:1"48ALL_TESTS="$ALL_TESTS 0005:10:1"49ALL_TESTS="$ALL_TESTS 0006:10:1"50ALL_TESTS="$ALL_TESTS 0007:5:1"51ALL_TESTS="$ALL_TESTS 0008:150:1"52ALL_TESTS="$ALL_TESTS 0009:150:1"53ALL_TESTS="$ALL_TESTS 0010:1:1"54ALL_TESTS="$ALL_TESTS 0011:1:1"55ALL_TESTS="$ALL_TESTS 0012:1:1"56ALL_TESTS="$ALL_TESTS 0013:1:1"57 58# Kselftest framework requirement - SKIP code is 4.59ksft_skip=460 61test_modprobe()62{63 if [ ! -d $DIR ]; then64 echo "$0: $DIR not present" >&265 echo "You must have the following enabled in your kernel:" >&266 cat $TEST_DIR/config >&267 exit $ksft_skip68 fi69}70 71function allow_user_defaults()72{73 if [ -z $DEFAULT_KMOD_DRIVER ]; then74 DEFAULT_KMOD_DRIVER="test_module"75 fi76 77 if [ -z $DEFAULT_KMOD_FS ]; then78 DEFAULT_KMOD_FS="xfs"79 fi80 81 if [ -z $PROC_DIR ]; then82 PROC_DIR="/proc/sys/kernel/"83 fi84 85 if [ -z $MODPROBE_LIMIT ]; then86 MODPROBE_LIMIT=5087 fi88 89 if [ -z $DIR ]; then90 DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/"91 fi92 93 if [ -z $DEFAULT_NUM_TESTS ]; then94 DEFAULT_NUM_TESTS=15095 fi96 97 MODPROBE_LIMIT_FILE="${PROC_DIR}/kmod-limit"98}99 100test_reqs()101{102 if ! which modprobe 2> /dev/null > /dev/null; then103 echo "$0: You need modprobe installed" >&2104 exit $ksft_skip105 fi106 107 if ! which kmod 2> /dev/null > /dev/null; then108 echo "$0: You need kmod installed" >&2109 exit $ksft_skip110 fi111 112 # kmod 19 has a bad bug where it returns 0 when modprobe113 # gets called *even* if the module was not loaded due to114 # some bad heuristics. For details see:115 #116 # A work around is possible in-kernel but its rather117 # complex.118 KMOD_VERSION=$(kmod --version | awk '{print $3}')119 if [[ $KMOD_VERSION -le 19 ]]; then120 echo "$0: You need at least kmod 20" >&2121 echo "kmod <= 19 is buggy, for details see:" >&2122 echo "https://git.kernel.org/cgit/utils/kernel/kmod/kmod.git/commit/libkmod/libkmod-module.c?id=fd44a98ae2eb5eb32161088954ab21e58e19dfc4" >&2123 exit $ksft_skip124 fi125 126 uid=$(id -u)127 if [ $uid -ne 0 ]; then128 echo $msg must be run as root >&2129 exit $ksft_skip130 fi131}132 133function load_req_mod()134{135 trap "test_modprobe" EXIT136 137 if [ ! -d $DIR ]; then138 # Alanis: "Oh isn't it ironic?"139 modprobe $TEST_DRIVER140 fi141}142 143test_finish()144{145 echo "$MODPROBE" > /proc/sys/kernel/modprobe146 echo "Test completed"147}148 149errno_name_to_val()150{151 case "$1" in152 # kmod calls modprobe and upon of a module not found153 # modprobe returns just 1... However in the kernel we154 # *sometimes* see 256...155 MODULE_NOT_FOUND)156 echo 256;;157 SUCCESS)158 echo 0;;159 -EPERM)160 echo -1;;161 -ENOENT)162 echo -2;;163 -EINVAL)164 echo -22;;165 -ERR_ANY)166 echo -123456;;167 *)168 echo invalid;;169 esac170}171 172errno_val_to_name()173 case "$1" in174 256)175 echo MODULE_NOT_FOUND;;176 0)177 echo SUCCESS;;178 -1)179 echo -EPERM;;180 -2)181 echo -ENOENT;;182 -22)183 echo -EINVAL;;184 -123456)185 echo -ERR_ANY;;186 *)187 echo invalid;;188 esac189 190config_set_test_case_driver()191{192 if ! echo -n 1 >$DIR/config_test_case; then193 echo "$0: Unable to set to test case to driver" >&2194 exit 1195 fi196}197 198config_set_test_case_fs()199{200 if ! echo -n 2 >$DIR/config_test_case; then201 echo "$0: Unable to set to test case to fs" >&2202 exit 1203 fi204}205 206config_num_threads()207{208 if ! echo -n $1 >$DIR/config_num_threads; then209 echo "$0: Unable to set to number of threads" >&2210 exit 1211 fi212}213 214config_get_modprobe_limit()215{216 if [[ -f ${MODPROBE_LIMIT_FILE} ]] ; then217 MODPROBE_LIMIT=$(cat $MODPROBE_LIMIT_FILE)218 fi219 echo $MODPROBE_LIMIT220}221 222config_num_thread_limit_extra()223{224 MODPROBE_LIMIT=$(config_get_modprobe_limit)225 let EXTRA_LIMIT=$MODPROBE_LIMIT+$1226 config_num_threads $EXTRA_LIMIT227}228 229# For special characters use printf directly,230# refer to kmod_test_0001231config_set_driver()232{233 if ! echo -n $1 >$DIR/config_test_driver; then234 echo "$0: Unable to set driver" >&2235 exit 1236 fi237}238 239config_set_fs()240{241 if ! echo -n $1 >$DIR/config_test_fs; then242 echo "$0: Unable to set driver" >&2243 exit 1244 fi245}246 247config_get_driver()248{249 cat $DIR/config_test_driver250}251 252config_get_test_result()253{254 cat $DIR/test_result255}256 257config_reset()258{259 if ! echo -n "1" >"$DIR"/reset; then260 echo "$0: reset should have worked" >&2261 exit 1262 fi263}264 265config_show_config()266{267 echo "----------------------------------------------------"268 cat "$DIR"/config269 echo "----------------------------------------------------"270}271 272config_trigger()273{274 if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then275 echo "$1: FAIL - loading should have worked"276 config_show_config277 exit 1278 fi279 echo "$1: OK! - loading kmod test"280}281 282config_trigger_want_fail()283{284 if echo "1" > $DIR/trigger_config 2>/dev/null; then285 echo "$1: FAIL - test case was expected to fail"286 config_show_config287 exit 1288 fi289 echo "$1: OK! - kmod test case failed as expected"290}291 292config_expect_result()293{294 RC=$(config_get_test_result)295 RC_NAME=$(errno_val_to_name $RC)296 297 ERRNO_NAME=$2298 ERRNO=$(errno_name_to_val $ERRNO_NAME)299 300 if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then301 if [[ $RC -ge 0 ]]; then302 echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2303 config_show_config304 exit 1305 fi306 elif [[ $RC != $ERRNO ]]; then307 echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2308 config_show_config309 exit 1310 fi311 echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME"312}313 314kmod_defaults_driver()315{316 config_reset317 modprobe -r $DEFAULT_KMOD_DRIVER318 config_set_driver $DEFAULT_KMOD_DRIVER319}320 321kmod_defaults_fs()322{323 config_reset324 modprobe -r $DEFAULT_KMOD_FS325 config_set_fs $DEFAULT_KMOD_FS326 config_set_test_case_fs327}328 329kmod_test_0001_driver()330{331 NAME='\000'332 333 kmod_defaults_driver334 config_num_threads 1335 printf $NAME >"$DIR"/config_test_driver336 config_trigger ${FUNCNAME[0]}337 config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND338}339 340kmod_test_0001_fs()341{342 NAME='\000'343 344 kmod_defaults_fs345 config_num_threads 1346 printf $NAME >"$DIR"/config_test_fs347 config_trigger ${FUNCNAME[0]}348 config_expect_result ${FUNCNAME[0]} -EINVAL349}350 351kmod_test_0001()352{353 kmod_test_0001_driver354 kmod_test_0001_fs355}356 357kmod_test_0002_driver()358{359 NAME="nope-$DEFAULT_KMOD_DRIVER"360 361 kmod_defaults_driver362 config_set_driver $NAME363 config_num_threads 1364 config_trigger ${FUNCNAME[0]}365 config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND366}367 368kmod_test_0002_fs()369{370 NAME="nope-$DEFAULT_KMOD_FS"371 372 kmod_defaults_fs373 config_set_fs $NAME374 config_trigger ${FUNCNAME[0]}375 config_expect_result ${FUNCNAME[0]} -EINVAL376}377 378kmod_test_0002()379{380 kmod_test_0002_driver381 kmod_test_0002_fs382}383 384kmod_test_0003()385{386 kmod_defaults_fs387 config_num_threads 1388 config_trigger ${FUNCNAME[0]}389 config_expect_result ${FUNCNAME[0]} SUCCESS390}391 392kmod_test_0004()393{394 kmod_defaults_fs395 config_num_threads 2396 config_trigger ${FUNCNAME[0]}397 config_expect_result ${FUNCNAME[0]} SUCCESS398}399 400kmod_test_0005()401{402 kmod_defaults_driver403 config_trigger ${FUNCNAME[0]}404 config_expect_result ${FUNCNAME[0]} SUCCESS405}406 407kmod_test_0006()408{409 kmod_defaults_fs410 config_trigger ${FUNCNAME[0]}411 config_expect_result ${FUNCNAME[0]} SUCCESS412}413 414kmod_test_0007()415{416 kmod_test_0005417 kmod_test_0006418}419 420kmod_test_0008()421{422 kmod_defaults_driver423 MODPROBE_LIMIT=$(config_get_modprobe_limit)424 let EXTRA=$MODPROBE_LIMIT/6425 config_num_thread_limit_extra $EXTRA426 config_trigger ${FUNCNAME[0]}427 config_expect_result ${FUNCNAME[0]} SUCCESS428}429 430kmod_test_0009()431{432 kmod_defaults_fs433 MODPROBE_LIMIT=$(config_get_modprobe_limit)434 let EXTRA=$MODPROBE_LIMIT/4435 config_num_thread_limit_extra $EXTRA436 config_trigger ${FUNCNAME[0]}437 config_expect_result ${FUNCNAME[0]} SUCCESS438}439 440kmod_test_0010()441{442 kmod_defaults_driver443 config_num_threads 1444 echo "/KMOD_TEST_NONEXISTENT" > /proc/sys/kernel/modprobe445 config_trigger ${FUNCNAME[0]}446 config_expect_result ${FUNCNAME[0]} -ENOENT447 echo "$MODPROBE" > /proc/sys/kernel/modprobe448}449 450kmod_test_0011()451{452 kmod_defaults_driver453 config_num_threads 1454 # This causes the kernel to not even try executing modprobe. The error455 # code is still -ENOENT like when modprobe doesn't exist, so we can't456 # easily test for the exact difference. But this still is a useful test457 # since there was a bug where request_module() returned 0 in this case.458 echo > /proc/sys/kernel/modprobe459 config_trigger ${FUNCNAME[0]}460 config_expect_result ${FUNCNAME[0]} -ENOENT461 echo "$MODPROBE" > /proc/sys/kernel/modprobe462}463 464kmod_check_visibility()465{466 local name="$1"467 local cmd="$2"468 469 modprobe $DEFAULT_KMOD_DRIVER470 471 local priv=$(eval $cmd)472 local unpriv=$(capsh --drop=CAP_SYSLOG -- -c "$cmd")473 474 if [ "$priv" = "$unpriv" ] || \475 [ "${priv:0:3}" = "0x0" ] || \476 [ "${unpriv:0:3}" != "0x0" ] ; then477 echo "${FUNCNAME[0]}: FAIL, $name visible to unpriv: '$priv' vs '$unpriv'" >&2478 exit 1479 else480 echo "${FUNCNAME[0]}: OK!"481 fi482}483 484kmod_test_0012()485{486 kmod_check_visibility /proc/modules \487 "grep '^${DEFAULT_KMOD_DRIVER}\b' /proc/modules | awk '{print \$NF}'"488}489 490kmod_test_0013()491{492 kmod_check_visibility '/sys/module/*/sections/*' \493 "cat /sys/module/${DEFAULT_KMOD_DRIVER}/sections/.*text | head -n1"494}495 496list_tests()497{498 echo "Test ID list:"499 echo500 echo "TEST_ID x NUM_TEST"501 echo "TEST_ID: Test ID"502 echo "NUM_TESTS: Number of recommended times to run the test"503 echo504 echo "0001 x $(get_test_count 0001) - Simple test - 1 thread for empty string"505 echo "0002 x $(get_test_count 0002) - Simple test - 1 thread for modules/filesystems that do not exist"506 echo "0003 x $(get_test_count 0003) - Simple test - 1 thread for get_fs_type() only"507 echo "0004 x $(get_test_count 0004) - Simple test - 2 threads for get_fs_type() only"508 echo "0005 x $(get_test_count 0005) - multithreaded tests with default setup - request_module() only"509 echo "0006 x $(get_test_count 0006) - multithreaded tests with default setup - get_fs_type() only"510 echo "0007 x $(get_test_count 0007) - multithreaded tests with default setup test request_module() and get_fs_type()"511 echo "0008 x $(get_test_count 0008) - multithreaded - push kmod_concurrent over max_modprobes for request_module()"512 echo "0009 x $(get_test_count 0009) - multithreaded - push kmod_concurrent over max_modprobes for get_fs_type()"513 echo "0010 x $(get_test_count 0010) - test nonexistent modprobe path"514 echo "0011 x $(get_test_count 0011) - test completely disabling module autoloading"515 echo "0012 x $(get_test_count 0012) - test /proc/modules address visibility under CAP_SYSLOG"516 echo "0013 x $(get_test_count 0013) - test /sys/module/*/sections/* visibility under CAP_SYSLOG"517}518 519usage()520{521 NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .)522 let NUM_TESTS=$NUM_TESTS+1523 MAX_TEST=$(printf "%04d\n" $NUM_TESTS)524 echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |"525 echo " [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>"526 echo " [ all ] [ -h | --help ] [ -l ]"527 echo ""528 echo "Valid tests: 0001-$MAX_TEST"529 echo ""530 echo " all Runs all tests (default)"531 echo " -t Run test ID the number amount of times is recommended"532 echo " -w Watch test ID run until it runs into an error"533 echo " -s Run test ID once"534 echo " -c Run test ID x test-count number of times"535 echo " -l List all test ID list"536 echo " -h|--help Help"537 echo538 echo "If an error every occurs execution will immediately terminate."539 echo "If you are adding a new test try using -w <test-ID> first to"540 echo "make sure the test passes a series of tests."541 echo542 echo Example uses:543 echo544 echo "${TEST_NAME}.sh -- executes all tests"545 echo "${TEST_NAME}.sh -t 0008 -- Executes test ID 0008 number of times is recommended"546 echo "${TEST_NAME}.sh -w 0008 -- Watch test ID 0008 run until an error occurs"547 echo "${TEST_NAME}.sh -s 0008 -- Run test ID 0008 once"548 echo "${TEST_NAME}.sh -c 0008 3 -- Run test ID 0008 three times"549 echo550 list_tests551 exit 1552}553 554function test_num()555{556 re='^[0-9]+$'557 if ! [[ $1 =~ $re ]]; then558 usage559 fi560}561 562function get_test_data()563{564 test_num $1565 local field_num=$(echo $1 | sed 's/^0*//')566 echo $ALL_TESTS | awk '{print $'$field_num'}'567}568 569function get_test_count()570{571 TEST_DATA=$(get_test_data $1)572 LAST_TWO=${TEST_DATA#*:*}573 echo ${LAST_TWO%:*}574}575 576function get_test_enabled()577{578 TEST_DATA=$(get_test_data $1)579 echo ${TEST_DATA#*:*:}580}581 582function run_all_tests()583{584 for i in $ALL_TESTS ; do585 TEST_ID=${i%:*:*}586 ENABLED=$(get_test_enabled $TEST_ID)587 TEST_COUNT=$(get_test_count $TEST_ID)588 if [[ $ENABLED -eq "1" ]]; then589 test_case $TEST_ID $TEST_COUNT590 fi591 done592}593 594function watch_log()595{596 if [ $# -ne 3 ]; then597 clear598 fi599 date600 echo "Running test: $2 - run #$1"601}602 603function watch_case()604{605 i=0606 while [ 1 ]; do607 608 if [ $# -eq 1 ]; then609 test_num $1610 watch_log $i ${TEST_NAME}_test_$1611 ${TEST_NAME}_test_$1612 else613 watch_log $i all614 run_all_tests615 fi616 let i=$i+1617 done618}619 620function test_case()621{622 NUM_TESTS=$DEFAULT_NUM_TESTS623 if [ $# -eq 2 ]; then624 NUM_TESTS=$2625 fi626 627 i=0628 while [ $i -lt $NUM_TESTS ]; do629 test_num $1630 watch_log $i ${TEST_NAME}_test_$1 noclear631 RUN_TEST=${TEST_NAME}_test_$1632 $RUN_TEST633 let i=$i+1634 done635}636 637function parse_args()638{639 if [ $# -eq 0 ]; then640 run_all_tests641 else642 if [[ "$1" = "all" ]]; then643 run_all_tests644 elif [[ "$1" = "-w" ]]; then645 shift646 watch_case $@647 elif [[ "$1" = "-t" ]]; then648 shift649 test_num $1650 test_case $1 $(get_test_count $1)651 elif [[ "$1" = "-c" ]]; then652 shift653 test_num $1654 test_num $2655 test_case $1 $2656 elif [[ "$1" = "-s" ]]; then657 shift658 test_case $1 1659 elif [[ "$1" = "-l" ]]; then660 list_tests661 elif [[ "$1" = "-h" || "$1" = "--help" ]]; then662 usage663 else664 usage665 fi666 fi667}668 669test_reqs670allow_user_defaults671load_req_mod672 673MODPROBE=$(</proc/sys/kernel/modprobe)674trap "test_finish" EXIT675 676parse_args $@677 678exit 0679