162306a36Sopenharmony_ci#!/bin/bash
262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
362306a36Sopenharmony_ci# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci# Shell functions for the rest of the scripts.
662306a36Sopenharmony_ci
762306a36Sopenharmony_ciMAX_RETRIES=600
862306a36Sopenharmony_ciRETRY_INTERVAL=".1"	# seconds
962306a36Sopenharmony_ciKLP_SYSFS_DIR="/sys/kernel/livepatch"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci# Kselftest framework requirement - SKIP code is 4
1262306a36Sopenharmony_ciksft_skip=4
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci# log(msg) - write message to kernel log
1562306a36Sopenharmony_ci#	msg - insightful words
1662306a36Sopenharmony_cifunction log() {
1762306a36Sopenharmony_ci	echo "$1" > /dev/kmsg
1862306a36Sopenharmony_ci}
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci# skip(msg) - testing can't proceed
2162306a36Sopenharmony_ci#	msg - explanation
2262306a36Sopenharmony_cifunction skip() {
2362306a36Sopenharmony_ci	log "SKIP: $1"
2462306a36Sopenharmony_ci	echo "SKIP: $1" >&2
2562306a36Sopenharmony_ci	exit $ksft_skip
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci# root test
2962306a36Sopenharmony_cifunction is_root() {
3062306a36Sopenharmony_ci	uid=$(id -u)
3162306a36Sopenharmony_ci	if [ $uid -ne 0 ]; then
3262306a36Sopenharmony_ci		echo "skip all tests: must be run as root" >&2
3362306a36Sopenharmony_ci		exit $ksft_skip
3462306a36Sopenharmony_ci	fi
3562306a36Sopenharmony_ci}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci# die(msg) - game over, man
3862306a36Sopenharmony_ci#	msg - dying words
3962306a36Sopenharmony_cifunction die() {
4062306a36Sopenharmony_ci	log "ERROR: $1"
4162306a36Sopenharmony_ci	echo "ERROR: $1" >&2
4262306a36Sopenharmony_ci	exit 1
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci# save existing dmesg so we can detect new content
4662306a36Sopenharmony_cifunction save_dmesg() {
4762306a36Sopenharmony_ci	SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX)
4862306a36Sopenharmony_ci	dmesg > "$SAVED_DMESG"
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci# cleanup temporary dmesg file from save_dmesg()
5262306a36Sopenharmony_cifunction cleanup_dmesg_file() {
5362306a36Sopenharmony_ci	rm -f "$SAVED_DMESG"
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cifunction push_config() {
5762306a36Sopenharmony_ci	DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \
5862306a36Sopenharmony_ci			awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')
5962306a36Sopenharmony_ci	FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cifunction pop_config() {
6362306a36Sopenharmony_ci	if [[ -n "$DYNAMIC_DEBUG" ]]; then
6462306a36Sopenharmony_ci		echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control
6562306a36Sopenharmony_ci	fi
6662306a36Sopenharmony_ci	if [[ -n "$FTRACE_ENABLED" ]]; then
6762306a36Sopenharmony_ci		sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
6862306a36Sopenharmony_ci	fi
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cifunction set_dynamic_debug() {
7262306a36Sopenharmony_ci        cat <<-EOF > /sys/kernel/debug/dynamic_debug/control
7362306a36Sopenharmony_ci		file kernel/livepatch/* +p
7462306a36Sopenharmony_ci		func klp_try_switch_task -p
7562306a36Sopenharmony_ci		EOF
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cifunction set_ftrace_enabled() {
7962306a36Sopenharmony_ci	local can_fail=0
8062306a36Sopenharmony_ci	if [[ "$1" == "--fail" ]] ; then
8162306a36Sopenharmony_ci		can_fail=1
8262306a36Sopenharmony_ci		shift
8362306a36Sopenharmony_ci	fi
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)
8662306a36Sopenharmony_ci	local result=$(sysctl --values kernel.ftrace_enabled)
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if [[ "$result" != "$1" ]] ; then
8962306a36Sopenharmony_ci		if [[ $can_fail -eq 1 ]] ; then
9062306a36Sopenharmony_ci			echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg
9162306a36Sopenharmony_ci			return
9262306a36Sopenharmony_ci		fi
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci		skip "failed to set kernel.ftrace_enabled = $1"
9562306a36Sopenharmony_ci	fi
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cifunction cleanup() {
10162306a36Sopenharmony_ci	pop_config
10262306a36Sopenharmony_ci	cleanup_dmesg_file
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci# setup_config - save the current config and set a script exit trap that
10662306a36Sopenharmony_ci#		 restores the original config.  Setup the dynamic debug
10762306a36Sopenharmony_ci#		 for verbose livepatching output and turn on
10862306a36Sopenharmony_ci#		 the ftrace_enabled sysctl.
10962306a36Sopenharmony_cifunction setup_config() {
11062306a36Sopenharmony_ci	is_root
11162306a36Sopenharmony_ci	push_config
11262306a36Sopenharmony_ci	set_dynamic_debug
11362306a36Sopenharmony_ci	set_ftrace_enabled 1
11462306a36Sopenharmony_ci	trap cleanup EXIT INT TERM HUP
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
11862306a36Sopenharmony_ci#		    sleep $RETRY_INTERVAL between attempts
11962306a36Sopenharmony_ci#	cmd - command and its arguments to run
12062306a36Sopenharmony_cifunction loop_until() {
12162306a36Sopenharmony_ci	local cmd="$*"
12262306a36Sopenharmony_ci	local i=0
12362306a36Sopenharmony_ci	while true; do
12462306a36Sopenharmony_ci		eval "$cmd" && return 0
12562306a36Sopenharmony_ci		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
12662306a36Sopenharmony_ci		sleep $RETRY_INTERVAL
12762306a36Sopenharmony_ci	done
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cifunction assert_mod() {
13162306a36Sopenharmony_ci	local mod="$1"
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	modprobe --dry-run "$mod" &>/dev/null
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cifunction is_livepatch_mod() {
13762306a36Sopenharmony_ci	local mod="$1"
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
14062306a36Sopenharmony_ci		return 0
14162306a36Sopenharmony_ci	fi
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	return 1
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cifunction __load_mod() {
14762306a36Sopenharmony_ci	local mod="$1"; shift
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	local msg="% modprobe $mod $*"
15062306a36Sopenharmony_ci	log "${msg%% }"
15162306a36Sopenharmony_ci	ret=$(modprobe "$mod" "$@" 2>&1)
15262306a36Sopenharmony_ci	if [[ "$ret" != "" ]]; then
15362306a36Sopenharmony_ci		die "$ret"
15462306a36Sopenharmony_ci	fi
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	# Wait for module in sysfs ...
15762306a36Sopenharmony_ci	loop_until '[[ -e "/sys/module/$mod" ]]' ||
15862306a36Sopenharmony_ci		die "failed to load module $mod"
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci# load_mod(modname, params) - load a kernel module
16362306a36Sopenharmony_ci#	modname - module name to load
16462306a36Sopenharmony_ci#	params  - module parameters to pass to modprobe
16562306a36Sopenharmony_cifunction load_mod() {
16662306a36Sopenharmony_ci	local mod="$1"; shift
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	assert_mod "$mod" ||
16962306a36Sopenharmony_ci		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	is_livepatch_mod "$mod" &&
17262306a36Sopenharmony_ci		die "use load_lp() to load the livepatch module $mod"
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	__load_mod "$mod" "$@"
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci# load_lp_nowait(modname, params) - load a kernel module with a livepatch
17862306a36Sopenharmony_ci#			but do not wait on until the transition finishes
17962306a36Sopenharmony_ci#	modname - module name to load
18062306a36Sopenharmony_ci#	params  - module parameters to pass to modprobe
18162306a36Sopenharmony_cifunction load_lp_nowait() {
18262306a36Sopenharmony_ci	local mod="$1"; shift
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	assert_mod "$mod" ||
18562306a36Sopenharmony_ci		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	is_livepatch_mod "$mod" ||
18862306a36Sopenharmony_ci		die "module $mod is not a livepatch"
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	__load_mod "$mod" "$@"
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	# Wait for livepatch in sysfs ...
19362306a36Sopenharmony_ci	loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
19462306a36Sopenharmony_ci		die "failed to load module $mod (sysfs)"
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci# load_lp(modname, params) - load a kernel module with a livepatch
19862306a36Sopenharmony_ci#	modname - module name to load
19962306a36Sopenharmony_ci#	params  - module parameters to pass to modprobe
20062306a36Sopenharmony_cifunction load_lp() {
20162306a36Sopenharmony_ci	local mod="$1"; shift
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	load_lp_nowait "$mod" "$@"
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	# Wait until the transition finishes ...
20662306a36Sopenharmony_ci	loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
20762306a36Sopenharmony_ci		die "failed to complete transition"
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci# load_failing_mod(modname, params) - load a kernel module, expect to fail
21162306a36Sopenharmony_ci#	modname - module name to load
21262306a36Sopenharmony_ci#	params  - module parameters to pass to modprobe
21362306a36Sopenharmony_cifunction load_failing_mod() {
21462306a36Sopenharmony_ci	local mod="$1"; shift
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	local msg="% modprobe $mod $*"
21762306a36Sopenharmony_ci	log "${msg%% }"
21862306a36Sopenharmony_ci	ret=$(modprobe "$mod" "$@" 2>&1)
21962306a36Sopenharmony_ci	if [[ "$ret" == "" ]]; then
22062306a36Sopenharmony_ci		die "$mod unexpectedly loaded"
22162306a36Sopenharmony_ci	fi
22262306a36Sopenharmony_ci	log "$ret"
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci# unload_mod(modname) - unload a kernel module
22662306a36Sopenharmony_ci#	modname - module name to unload
22762306a36Sopenharmony_cifunction unload_mod() {
22862306a36Sopenharmony_ci	local mod="$1"
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	# Wait for module reference count to clear ...
23162306a36Sopenharmony_ci	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
23262306a36Sopenharmony_ci		die "failed to unload module $mod (refcnt)"
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	log "% rmmod $mod"
23562306a36Sopenharmony_ci	ret=$(rmmod "$mod" 2>&1)
23662306a36Sopenharmony_ci	if [[ "$ret" != "" ]]; then
23762306a36Sopenharmony_ci		die "$ret"
23862306a36Sopenharmony_ci	fi
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	# Wait for module in sysfs ...
24162306a36Sopenharmony_ci	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
24262306a36Sopenharmony_ci		die "failed to unload module $mod (/sys/module)"
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci# unload_lp(modname) - unload a kernel module with a livepatch
24662306a36Sopenharmony_ci#	modname - module name to unload
24762306a36Sopenharmony_cifunction unload_lp() {
24862306a36Sopenharmony_ci	unload_mod "$1"
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci# disable_lp(modname) - disable a livepatch
25262306a36Sopenharmony_ci#	modname - module name to unload
25362306a36Sopenharmony_cifunction disable_lp() {
25462306a36Sopenharmony_ci	local mod="$1"
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
25762306a36Sopenharmony_ci	echo 0 > /sys/kernel/livepatch/"$mod"/enabled
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	# Wait until the transition finishes and the livepatch gets
26062306a36Sopenharmony_ci	# removed from sysfs...
26162306a36Sopenharmony_ci	loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
26262306a36Sopenharmony_ci		die "failed to disable livepatch $mod"
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci# set_pre_patch_ret(modname, pre_patch_ret)
26662306a36Sopenharmony_ci#	modname - module name to set
26762306a36Sopenharmony_ci#	pre_patch_ret - new pre_patch_ret value
26862306a36Sopenharmony_cifunction set_pre_patch_ret {
26962306a36Sopenharmony_ci	local mod="$1"; shift
27062306a36Sopenharmony_ci	local ret="$1"
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
27362306a36Sopenharmony_ci	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	# Wait for sysfs value to hold ...
27662306a36Sopenharmony_ci	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
27762306a36Sopenharmony_ci		die "failed to set pre_patch_ret parameter for $mod module"
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cifunction start_test {
28162306a36Sopenharmony_ci	local test="$1"
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	save_dmesg
28462306a36Sopenharmony_ci	echo -n "TEST: $test ... "
28562306a36Sopenharmony_ci	log "===== TEST: $test ====="
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci# check_result() - verify dmesg output
28962306a36Sopenharmony_ci#	TODO - better filter, out of order msgs, etc?
29062306a36Sopenharmony_cifunction check_result {
29162306a36Sopenharmony_ci	local expect="$*"
29262306a36Sopenharmony_ci	local result
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	# Note: when comparing dmesg output, the kernel log timestamps
29562306a36Sopenharmony_ci	# help differentiate repeated testing runs.  Remove them with a
29662306a36Sopenharmony_ci	# post-comparison sed filter.
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \
29962306a36Sopenharmony_ci		 grep -e 'livepatch:' -e 'test_klp' | \
30062306a36Sopenharmony_ci		 grep -v '\(tainting\|taints\) kernel' | \
30162306a36Sopenharmony_ci		 sed 's/^\[[ 0-9.]*\] //')
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	if [[ "$expect" == "$result" ]] ; then
30462306a36Sopenharmony_ci		echo "ok"
30562306a36Sopenharmony_ci	else
30662306a36Sopenharmony_ci		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
30762306a36Sopenharmony_ci		die "livepatch kselftest(s) failed"
30862306a36Sopenharmony_ci	fi
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	cleanup_dmesg_file
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs
31462306a36Sopenharmony_ci# path permissions
31562306a36Sopenharmony_ci#	modname - livepatch module creating the sysfs interface
31662306a36Sopenharmony_ci#	rel_path - relative path of the sysfs interface
31762306a36Sopenharmony_ci#	expected_rights - expected access rights
31862306a36Sopenharmony_cifunction check_sysfs_rights() {
31962306a36Sopenharmony_ci	local mod="$1"; shift
32062306a36Sopenharmony_ci	local rel_path="$1"; shift
32162306a36Sopenharmony_ci	local expected_rights="$1"; shift
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	local path="$KLP_SYSFS_DIR/$mod/$rel_path"
32462306a36Sopenharmony_ci	local rights=$(/bin/stat --format '%A' "$path")
32562306a36Sopenharmony_ci	if test "$rights" != "$expected_rights" ; then
32662306a36Sopenharmony_ci		die "Unexpected access rights of $path: $expected_rights vs. $rights"
32762306a36Sopenharmony_ci	fi
32862306a36Sopenharmony_ci}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value
33162306a36Sopenharmony_ci#	modname - livepatch module creating the sysfs interface
33262306a36Sopenharmony_ci#	rel_path - relative path of the sysfs interface
33362306a36Sopenharmony_ci#	expected_value - expected value read from the file
33462306a36Sopenharmony_cifunction check_sysfs_value() {
33562306a36Sopenharmony_ci	local mod="$1"; shift
33662306a36Sopenharmony_ci	local rel_path="$1"; shift
33762306a36Sopenharmony_ci	local expected_value="$1"; shift
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	local path="$KLP_SYSFS_DIR/$mod/$rel_path"
34062306a36Sopenharmony_ci	local value=`cat $path`
34162306a36Sopenharmony_ci	if test "$value" != "$expected_value" ; then
34262306a36Sopenharmony_ci		die "Unexpected value in $path: $expected_value vs. $value"
34362306a36Sopenharmony_ci	fi
34462306a36Sopenharmony_ci}
345