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