1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0-or-later OR copyleft-next-0.3.1 3# Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org> 4# 5# This is a stress test script for kmod, the kernel module loader. It uses 6# test_kmod which exposes a series of knobs for the API for us so we can 7# 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 eventually 10# call /sbin/modprobe. It has a limit of the number of concurrent calls 11# possible. The kernel interface to load modules is request_module(), however 12# mount uses get_fs_type(). Both behave slightly differently, but the 13# differences are important enough to test each call separately. For this 14# reason test_kmod starts by providing tests for both calls. 15# 16# The test driver test_kmod assumes a series of defaults which you can 17# 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 this 20# script if the filesystem module you don't have loaded upon bootup 21# is ext4 instead. Refer to allow_user_defaults() for a list of user 22# override variables possible. 23# 24# You'll want at least 4 GiB of RAM to expect to run these tests 25# without running out of memory on them. For other requirements refer 26# to test_reqs() 27 28set -e 29 30TEST_NAME="kmod" 31TEST_DRIVER="test_${TEST_NAME}" 32TEST_DIR=$(dirname $0) 33 34# This represents 35# 36# TEST_ID:TEST_COUNT:ENABLED 37# 38# TEST_ID: is the test id number 39# TEST_COUNT: number of times we should run the test 40# ENABLED: 1 if enabled, 0 otherwise 41# 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=4 60 61test_modprobe() 62{ 63 if [ ! -d $DIR ]; then 64 echo "$0: $DIR not present" >&2 65 echo "You must have the following enabled in your kernel:" >&2 66 cat $TEST_DIR/config >&2 67 exit $ksft_skip 68 fi 69} 70 71function allow_user_defaults() 72{ 73 if [ -z $DEFAULT_KMOD_DRIVER ]; then 74 DEFAULT_KMOD_DRIVER="test_module" 75 fi 76 77 if [ -z $DEFAULT_KMOD_FS ]; then 78 DEFAULT_KMOD_FS="xfs" 79 fi 80 81 if [ -z $PROC_DIR ]; then 82 PROC_DIR="/proc/sys/kernel/" 83 fi 84 85 if [ -z $MODPROBE_LIMIT ]; then 86 MODPROBE_LIMIT=50 87 fi 88 89 if [ -z $DIR ]; then 90 DIR="/sys/devices/virtual/misc/${TEST_DRIVER}0/" 91 fi 92 93 if [ -z $DEFAULT_NUM_TESTS ]; then 94 DEFAULT_NUM_TESTS=150 95 fi 96 97 MODPROBE_LIMIT_FILE="${PROC_DIR}/kmod-limit" 98} 99 100test_reqs() 101{ 102 if ! which modprobe 2> /dev/null > /dev/null; then 103 echo "$0: You need modprobe installed" >&2 104 exit $ksft_skip 105 fi 106 107 if ! which kmod 2> /dev/null > /dev/null; then 108 echo "$0: You need kmod installed" >&2 109 exit $ksft_skip 110 fi 111 112 # kmod 19 has a bad bug where it returns 0 when modprobe 113 # gets called *even* if the module was not loaded due to 114 # some bad heuristics. For details see: 115 # 116 # A work around is possible in-kernel but its rather 117 # complex. 118 KMOD_VERSION=$(kmod --version | awk '{print $3}') 119 if [[ $KMOD_VERSION -le 19 ]]; then 120 echo "$0: You need at least kmod 20" >&2 121 echo "kmod <= 19 is buggy, for details see:" >&2 122 echo "https://git.kernel.org/cgit/utils/kernel/kmod/kmod.git/commit/libkmod/libkmod-module.c?id=fd44a98ae2eb5eb32161088954ab21e58e19dfc4" >&2 123 exit $ksft_skip 124 fi 125 126 uid=$(id -u) 127 if [ $uid -ne 0 ]; then 128 echo $msg must be run as root >&2 129 exit $ksft_skip 130 fi 131} 132 133function load_req_mod() 134{ 135 trap "test_modprobe" EXIT 136 137 if [ ! -d $DIR ]; then 138 # Alanis: "Oh isn't it ironic?" 139 modprobe $TEST_DRIVER 140 fi 141} 142 143test_finish() 144{ 145 echo "$MODPROBE" > /proc/sys/kernel/modprobe 146 echo "Test completed" 147} 148 149errno_name_to_val() 150{ 151 case "$1" in 152 # kmod calls modprobe and upon of a module not found 153 # modprobe returns just 1... However in the kernel we 154 # *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 esac 170} 171 172errno_val_to_name() 173 case "$1" in 174 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 esac 189 190config_set_test_case_driver() 191{ 192 if ! echo -n 1 >$DIR/config_test_case; then 193 echo "$0: Unable to set to test case to driver" >&2 194 exit 1 195 fi 196} 197 198config_set_test_case_fs() 199{ 200 if ! echo -n 2 >$DIR/config_test_case; then 201 echo "$0: Unable to set to test case to fs" >&2 202 exit 1 203 fi 204} 205 206config_num_threads() 207{ 208 if ! echo -n $1 >$DIR/config_num_threads; then 209 echo "$0: Unable to set to number of threads" >&2 210 exit 1 211 fi 212} 213 214config_get_modprobe_limit() 215{ 216 if [[ -f ${MODPROBE_LIMIT_FILE} ]] ; then 217 MODPROBE_LIMIT=$(cat $MODPROBE_LIMIT_FILE) 218 fi 219 echo $MODPROBE_LIMIT 220} 221 222config_num_thread_limit_extra() 223{ 224 MODPROBE_LIMIT=$(config_get_modprobe_limit) 225 let EXTRA_LIMIT=$MODPROBE_LIMIT+$1 226 config_num_threads $EXTRA_LIMIT 227} 228 229# For special characters use printf directly, 230# refer to kmod_test_0001 231config_set_driver() 232{ 233 if ! echo -n $1 >$DIR/config_test_driver; then 234 echo "$0: Unable to set driver" >&2 235 exit 1 236 fi 237} 238 239config_set_fs() 240{ 241 if ! echo -n $1 >$DIR/config_test_fs; then 242 echo "$0: Unable to set driver" >&2 243 exit 1 244 fi 245} 246 247config_get_driver() 248{ 249 cat $DIR/config_test_driver 250} 251 252config_get_test_result() 253{ 254 cat $DIR/test_result 255} 256 257config_reset() 258{ 259 if ! echo -n "1" >"$DIR"/reset; then 260 echo "$0: reset should have worked" >&2 261 exit 1 262 fi 263} 264 265config_show_config() 266{ 267 echo "----------------------------------------------------" 268 cat "$DIR"/config 269 echo "----------------------------------------------------" 270} 271 272config_trigger() 273{ 274 if ! echo -n "1" >"$DIR"/trigger_config 2>/dev/null; then 275 echo "$1: FAIL - loading should have worked" 276 config_show_config 277 exit 1 278 fi 279 echo "$1: OK! - loading kmod test" 280} 281 282config_trigger_want_fail() 283{ 284 if echo "1" > $DIR/trigger_config 2>/dev/null; then 285 echo "$1: FAIL - test case was expected to fail" 286 config_show_config 287 exit 1 288 fi 289 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=$2 298 ERRNO=$(errno_name_to_val $ERRNO_NAME) 299 300 if [[ $ERRNO_NAME = "-ERR_ANY" ]]; then 301 if [[ $RC -ge 0 ]]; then 302 echo "$1: FAIL, test expects $ERRNO_NAME - got $RC_NAME ($RC)" >&2 303 config_show_config 304 exit 1 305 fi 306 elif [[ $RC != $ERRNO ]]; then 307 echo "$1: FAIL, test expects $ERRNO_NAME ($ERRNO) - got $RC_NAME ($RC)" >&2 308 config_show_config 309 exit 1 310 fi 311 echo "$1: OK! - Return value: $RC ($RC_NAME), expected $ERRNO_NAME" 312} 313 314kmod_defaults_driver() 315{ 316 config_reset 317 modprobe -r $DEFAULT_KMOD_DRIVER 318 config_set_driver $DEFAULT_KMOD_DRIVER 319} 320 321kmod_defaults_fs() 322{ 323 config_reset 324 modprobe -r $DEFAULT_KMOD_FS 325 config_set_fs $DEFAULT_KMOD_FS 326 config_set_test_case_fs 327} 328 329kmod_test_0001_driver() 330{ 331 NAME='\000' 332 333 kmod_defaults_driver 334 config_num_threads 1 335 printf $NAME >"$DIR"/config_test_driver 336 config_trigger ${FUNCNAME[0]} 337 config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND 338} 339 340kmod_test_0001_fs() 341{ 342 NAME='\000' 343 344 kmod_defaults_fs 345 config_num_threads 1 346 printf $NAME >"$DIR"/config_test_fs 347 config_trigger ${FUNCNAME[0]} 348 config_expect_result ${FUNCNAME[0]} -EINVAL 349} 350 351kmod_test_0001() 352{ 353 kmod_test_0001_driver 354 kmod_test_0001_fs 355} 356 357kmod_test_0002_driver() 358{ 359 NAME="nope-$DEFAULT_KMOD_DRIVER" 360 361 kmod_defaults_driver 362 config_set_driver $NAME 363 config_num_threads 1 364 config_trigger ${FUNCNAME[0]} 365 config_expect_result ${FUNCNAME[0]} MODULE_NOT_FOUND 366} 367 368kmod_test_0002_fs() 369{ 370 NAME="nope-$DEFAULT_KMOD_FS" 371 372 kmod_defaults_fs 373 config_set_fs $NAME 374 config_trigger ${FUNCNAME[0]} 375 config_expect_result ${FUNCNAME[0]} -EINVAL 376} 377 378kmod_test_0002() 379{ 380 kmod_test_0002_driver 381 kmod_test_0002_fs 382} 383 384kmod_test_0003() 385{ 386 kmod_defaults_fs 387 config_num_threads 1 388 config_trigger ${FUNCNAME[0]} 389 config_expect_result ${FUNCNAME[0]} SUCCESS 390} 391 392kmod_test_0004() 393{ 394 kmod_defaults_fs 395 config_num_threads 2 396 config_trigger ${FUNCNAME[0]} 397 config_expect_result ${FUNCNAME[0]} SUCCESS 398} 399 400kmod_test_0005() 401{ 402 kmod_defaults_driver 403 config_trigger ${FUNCNAME[0]} 404 config_expect_result ${FUNCNAME[0]} SUCCESS 405} 406 407kmod_test_0006() 408{ 409 kmod_defaults_fs 410 config_trigger ${FUNCNAME[0]} 411 config_expect_result ${FUNCNAME[0]} SUCCESS 412} 413 414kmod_test_0007() 415{ 416 kmod_test_0005 417 kmod_test_0006 418} 419 420kmod_test_0008() 421{ 422 kmod_defaults_driver 423 MODPROBE_LIMIT=$(config_get_modprobe_limit) 424 let EXTRA=$MODPROBE_LIMIT/6 425 config_num_thread_limit_extra $EXTRA 426 config_trigger ${FUNCNAME[0]} 427 config_expect_result ${FUNCNAME[0]} SUCCESS 428} 429 430kmod_test_0009() 431{ 432 kmod_defaults_fs 433 MODPROBE_LIMIT=$(config_get_modprobe_limit) 434 let EXTRA=$MODPROBE_LIMIT/4 435 config_num_thread_limit_extra $EXTRA 436 config_trigger ${FUNCNAME[0]} 437 config_expect_result ${FUNCNAME[0]} SUCCESS 438} 439 440kmod_test_0010() 441{ 442 kmod_defaults_driver 443 config_num_threads 1 444 echo "/KMOD_TEST_NONEXISTENT" > /proc/sys/kernel/modprobe 445 config_trigger ${FUNCNAME[0]} 446 config_expect_result ${FUNCNAME[0]} -ENOENT 447 echo "$MODPROBE" > /proc/sys/kernel/modprobe 448} 449 450kmod_test_0011() 451{ 452 kmod_defaults_driver 453 config_num_threads 1 454 # This causes the kernel to not even try executing modprobe. The error 455 # code is still -ENOENT like when modprobe doesn't exist, so we can't 456 # easily test for the exact difference. But this still is a useful test 457 # since there was a bug where request_module() returned 0 in this case. 458 echo > /proc/sys/kernel/modprobe 459 config_trigger ${FUNCNAME[0]} 460 config_expect_result ${FUNCNAME[0]} -ENOENT 461 echo "$MODPROBE" > /proc/sys/kernel/modprobe 462} 463 464kmod_check_visibility() 465{ 466 local name="$1" 467 local cmd="$2" 468 469 modprobe $DEFAULT_KMOD_DRIVER 470 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" ] ; then 477 echo "${FUNCNAME[0]}: FAIL, $name visible to unpriv: '$priv' vs '$unpriv'" >&2 478 exit 1 479 else 480 echo "${FUNCNAME[0]}: OK!" 481 fi 482} 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 echo 500 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 echo 504 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+1 523 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 echo 538 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 echo 542 echo Example uses: 543 echo 544 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 echo 550 list_tests 551 exit 1 552} 553 554function test_num() 555{ 556 re='^[0-9]+$' 557 if ! [[ $1 =~ $re ]]; then 558 usage 559 fi 560} 561 562function get_test_data() 563{ 564 test_num $1 565 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 ; do 585 TEST_ID=${i%:*:*} 586 ENABLED=$(get_test_enabled $TEST_ID) 587 TEST_COUNT=$(get_test_count $TEST_ID) 588 if [[ $ENABLED -eq "1" ]]; then 589 test_case $TEST_ID $TEST_COUNT 590 fi 591 done 592} 593 594function watch_log() 595{ 596 if [ $# -ne 3 ]; then 597 clear 598 fi 599 date 600 echo "Running test: $2 - run #$1" 601} 602 603function watch_case() 604{ 605 i=0 606 while [ 1 ]; do 607 608 if [ $# -eq 1 ]; then 609 test_num $1 610 watch_log $i ${TEST_NAME}_test_$1 611 ${TEST_NAME}_test_$1 612 else 613 watch_log $i all 614 run_all_tests 615 fi 616 let i=$i+1 617 done 618} 619 620function test_case() 621{ 622 NUM_TESTS=$DEFAULT_NUM_TESTS 623 if [ $# -eq 2 ]; then 624 NUM_TESTS=$2 625 fi 626 627 i=0 628 while [ $i -lt $NUM_TESTS ]; do 629 test_num $1 630 watch_log $i ${TEST_NAME}_test_$1 noclear 631 RUN_TEST=${TEST_NAME}_test_$1 632 $RUN_TEST 633 let i=$i+1 634 done 635} 636 637function parse_args() 638{ 639 if [ $# -eq 0 ]; then 640 run_all_tests 641 else 642 if [[ "$1" = "all" ]]; then 643 run_all_tests 644 elif [[ "$1" = "-w" ]]; then 645 shift 646 watch_case $@ 647 elif [[ "$1" = "-t" ]]; then 648 shift 649 test_num $1 650 test_case $1 $(get_test_count $1) 651 elif [[ "$1" = "-c" ]]; then 652 shift 653 test_num $1 654 test_num $2 655 test_case $1 $2 656 elif [[ "$1" = "-s" ]]; then 657 shift 658 test_case $1 1 659 elif [[ "$1" = "-l" ]]; then 660 list_tests 661 elif [[ "$1" = "-h" || "$1" = "--help" ]]; then 662 usage 663 else 664 usage 665 fi 666 fi 667} 668 669test_reqs 670allow_user_defaults 671load_req_mod 672 673MODPROBE=$(</proc/sys/kernel/modprobe) 674trap "test_finish" EXIT 675 676parse_args $@ 677 678exit 0 679