18c2ecf20Sopenharmony_ci#!/bin/bash
28c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
38c2ecf20Sopenharmony_ci# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci# Shell functions for the rest of the scripts.
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ciMAX_RETRIES=600
88c2ecf20Sopenharmony_ciRETRY_INTERVAL=".1"	# seconds
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci# Kselftest framework requirement - SKIP code is 4
118c2ecf20Sopenharmony_ciksft_skip=4
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci# log(msg) - write message to kernel log
148c2ecf20Sopenharmony_ci#	msg - insightful words
158c2ecf20Sopenharmony_cifunction log() {
168c2ecf20Sopenharmony_ci	echo "$1" > /dev/kmsg
178c2ecf20Sopenharmony_ci}
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci# skip(msg) - testing can't proceed
208c2ecf20Sopenharmony_ci#	msg - explanation
218c2ecf20Sopenharmony_cifunction skip() {
228c2ecf20Sopenharmony_ci	log "SKIP: $1"
238c2ecf20Sopenharmony_ci	echo "SKIP: $1" >&2
248c2ecf20Sopenharmony_ci	exit $ksft_skip
258c2ecf20Sopenharmony_ci}
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci# root test
288c2ecf20Sopenharmony_cifunction is_root() {
298c2ecf20Sopenharmony_ci	uid=$(id -u)
308c2ecf20Sopenharmony_ci	if [ $uid -ne 0 ]; then
318c2ecf20Sopenharmony_ci		echo "skip all tests: must be run as root" >&2
328c2ecf20Sopenharmony_ci		exit $ksft_skip
338c2ecf20Sopenharmony_ci	fi
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci# die(msg) - game over, man
378c2ecf20Sopenharmony_ci#	msg - dying words
388c2ecf20Sopenharmony_cifunction die() {
398c2ecf20Sopenharmony_ci	log "ERROR: $1"
408c2ecf20Sopenharmony_ci	echo "ERROR: $1" >&2
418c2ecf20Sopenharmony_ci	exit 1
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci# save existing dmesg so we can detect new content
458c2ecf20Sopenharmony_cifunction save_dmesg() {
468c2ecf20Sopenharmony_ci	SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX)
478c2ecf20Sopenharmony_ci	dmesg > "$SAVED_DMESG"
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci# cleanup temporary dmesg file from save_dmesg()
518c2ecf20Sopenharmony_cifunction cleanup_dmesg_file() {
528c2ecf20Sopenharmony_ci	rm -f "$SAVED_DMESG"
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cifunction push_config() {
568c2ecf20Sopenharmony_ci	DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \
578c2ecf20Sopenharmony_ci			awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')
588c2ecf20Sopenharmony_ci	FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cifunction pop_config() {
628c2ecf20Sopenharmony_ci	if [[ -n "$DYNAMIC_DEBUG" ]]; then
638c2ecf20Sopenharmony_ci		echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control
648c2ecf20Sopenharmony_ci	fi
658c2ecf20Sopenharmony_ci	if [[ -n "$FTRACE_ENABLED" ]]; then
668c2ecf20Sopenharmony_ci		sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
678c2ecf20Sopenharmony_ci	fi
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cifunction set_dynamic_debug() {
718c2ecf20Sopenharmony_ci        cat <<-EOF > /sys/kernel/debug/dynamic_debug/control
728c2ecf20Sopenharmony_ci		file kernel/livepatch/* +p
738c2ecf20Sopenharmony_ci		func klp_try_switch_task -p
748c2ecf20Sopenharmony_ci		EOF
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cifunction set_ftrace_enabled() {
788c2ecf20Sopenharmony_ci	result=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1 && \
798c2ecf20Sopenharmony_ci		 sysctl kernel.ftrace_enabled 2>&1)
808c2ecf20Sopenharmony_ci	echo "livepatch: $result" > /dev/kmsg
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cifunction cleanup() {
848c2ecf20Sopenharmony_ci	pop_config
858c2ecf20Sopenharmony_ci	cleanup_dmesg_file
868c2ecf20Sopenharmony_ci}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci# setup_config - save the current config and set a script exit trap that
898c2ecf20Sopenharmony_ci#		 restores the original config.  Setup the dynamic debug
908c2ecf20Sopenharmony_ci#		 for verbose livepatching output and turn on
918c2ecf20Sopenharmony_ci#		 the ftrace_enabled sysctl.
928c2ecf20Sopenharmony_cifunction setup_config() {
938c2ecf20Sopenharmony_ci	is_root
948c2ecf20Sopenharmony_ci	push_config
958c2ecf20Sopenharmony_ci	set_dynamic_debug
968c2ecf20Sopenharmony_ci	set_ftrace_enabled 1
978c2ecf20Sopenharmony_ci	trap cleanup EXIT INT TERM HUP
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
1018c2ecf20Sopenharmony_ci#		    sleep $RETRY_INTERVAL between attempts
1028c2ecf20Sopenharmony_ci#	cmd - command and its arguments to run
1038c2ecf20Sopenharmony_cifunction loop_until() {
1048c2ecf20Sopenharmony_ci	local cmd="$*"
1058c2ecf20Sopenharmony_ci	local i=0
1068c2ecf20Sopenharmony_ci	while true; do
1078c2ecf20Sopenharmony_ci		eval "$cmd" && return 0
1088c2ecf20Sopenharmony_ci		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
1098c2ecf20Sopenharmony_ci		sleep $RETRY_INTERVAL
1108c2ecf20Sopenharmony_ci	done
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cifunction assert_mod() {
1148c2ecf20Sopenharmony_ci	local mod="$1"
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	modprobe --dry-run "$mod" &>/dev/null
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cifunction is_livepatch_mod() {
1208c2ecf20Sopenharmony_ci	local mod="$1"
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
1238c2ecf20Sopenharmony_ci		return 0
1248c2ecf20Sopenharmony_ci	fi
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	return 1
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cifunction __load_mod() {
1308c2ecf20Sopenharmony_ci	local mod="$1"; shift
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	local msg="% modprobe $mod $*"
1338c2ecf20Sopenharmony_ci	log "${msg%% }"
1348c2ecf20Sopenharmony_ci	ret=$(modprobe "$mod" "$@" 2>&1)
1358c2ecf20Sopenharmony_ci	if [[ "$ret" != "" ]]; then
1368c2ecf20Sopenharmony_ci		die "$ret"
1378c2ecf20Sopenharmony_ci	fi
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	# Wait for module in sysfs ...
1408c2ecf20Sopenharmony_ci	loop_until '[[ -e "/sys/module/$mod" ]]' ||
1418c2ecf20Sopenharmony_ci		die "failed to load module $mod"
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci# load_mod(modname, params) - load a kernel module
1468c2ecf20Sopenharmony_ci#	modname - module name to load
1478c2ecf20Sopenharmony_ci#	params  - module parameters to pass to modprobe
1488c2ecf20Sopenharmony_cifunction load_mod() {
1498c2ecf20Sopenharmony_ci	local mod="$1"; shift
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	assert_mod "$mod" ||
1528c2ecf20Sopenharmony_ci		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	is_livepatch_mod "$mod" &&
1558c2ecf20Sopenharmony_ci		die "use load_lp() to load the livepatch module $mod"
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	__load_mod "$mod" "$@"
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci# load_lp_nowait(modname, params) - load a kernel module with a livepatch
1618c2ecf20Sopenharmony_ci#			but do not wait on until the transition finishes
1628c2ecf20Sopenharmony_ci#	modname - module name to load
1638c2ecf20Sopenharmony_ci#	params  - module parameters to pass to modprobe
1648c2ecf20Sopenharmony_cifunction load_lp_nowait() {
1658c2ecf20Sopenharmony_ci	local mod="$1"; shift
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	assert_mod "$mod" ||
1688c2ecf20Sopenharmony_ci		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	is_livepatch_mod "$mod" ||
1718c2ecf20Sopenharmony_ci		die "module $mod is not a livepatch"
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	__load_mod "$mod" "$@"
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	# Wait for livepatch in sysfs ...
1768c2ecf20Sopenharmony_ci	loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
1778c2ecf20Sopenharmony_ci		die "failed to load module $mod (sysfs)"
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci# load_lp(modname, params) - load a kernel module with a livepatch
1818c2ecf20Sopenharmony_ci#	modname - module name to load
1828c2ecf20Sopenharmony_ci#	params  - module parameters to pass to modprobe
1838c2ecf20Sopenharmony_cifunction load_lp() {
1848c2ecf20Sopenharmony_ci	local mod="$1"; shift
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	load_lp_nowait "$mod" "$@"
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	# Wait until the transition finishes ...
1898c2ecf20Sopenharmony_ci	loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
1908c2ecf20Sopenharmony_ci		die "failed to complete transition"
1918c2ecf20Sopenharmony_ci}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci# load_failing_mod(modname, params) - load a kernel module, expect to fail
1948c2ecf20Sopenharmony_ci#	modname - module name to load
1958c2ecf20Sopenharmony_ci#	params  - module parameters to pass to modprobe
1968c2ecf20Sopenharmony_cifunction load_failing_mod() {
1978c2ecf20Sopenharmony_ci	local mod="$1"; shift
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	local msg="% modprobe $mod $*"
2008c2ecf20Sopenharmony_ci	log "${msg%% }"
2018c2ecf20Sopenharmony_ci	ret=$(modprobe "$mod" "$@" 2>&1)
2028c2ecf20Sopenharmony_ci	if [[ "$ret" == "" ]]; then
2038c2ecf20Sopenharmony_ci		die "$mod unexpectedly loaded"
2048c2ecf20Sopenharmony_ci	fi
2058c2ecf20Sopenharmony_ci	log "$ret"
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci# unload_mod(modname) - unload a kernel module
2098c2ecf20Sopenharmony_ci#	modname - module name to unload
2108c2ecf20Sopenharmony_cifunction unload_mod() {
2118c2ecf20Sopenharmony_ci	local mod="$1"
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	# Wait for module reference count to clear ...
2148c2ecf20Sopenharmony_ci	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
2158c2ecf20Sopenharmony_ci		die "failed to unload module $mod (refcnt)"
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	log "% rmmod $mod"
2188c2ecf20Sopenharmony_ci	ret=$(rmmod "$mod" 2>&1)
2198c2ecf20Sopenharmony_ci	if [[ "$ret" != "" ]]; then
2208c2ecf20Sopenharmony_ci		die "$ret"
2218c2ecf20Sopenharmony_ci	fi
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	# Wait for module in sysfs ...
2248c2ecf20Sopenharmony_ci	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
2258c2ecf20Sopenharmony_ci		die "failed to unload module $mod (/sys/module)"
2268c2ecf20Sopenharmony_ci}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci# unload_lp(modname) - unload a kernel module with a livepatch
2298c2ecf20Sopenharmony_ci#	modname - module name to unload
2308c2ecf20Sopenharmony_cifunction unload_lp() {
2318c2ecf20Sopenharmony_ci	unload_mod "$1"
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci# disable_lp(modname) - disable a livepatch
2358c2ecf20Sopenharmony_ci#	modname - module name to unload
2368c2ecf20Sopenharmony_cifunction disable_lp() {
2378c2ecf20Sopenharmony_ci	local mod="$1"
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
2408c2ecf20Sopenharmony_ci	echo 0 > /sys/kernel/livepatch/"$mod"/enabled
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	# Wait until the transition finishes and the livepatch gets
2438c2ecf20Sopenharmony_ci	# removed from sysfs...
2448c2ecf20Sopenharmony_ci	loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
2458c2ecf20Sopenharmony_ci		die "failed to disable livepatch $mod"
2468c2ecf20Sopenharmony_ci}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci# set_pre_patch_ret(modname, pre_patch_ret)
2498c2ecf20Sopenharmony_ci#	modname - module name to set
2508c2ecf20Sopenharmony_ci#	pre_patch_ret - new pre_patch_ret value
2518c2ecf20Sopenharmony_cifunction set_pre_patch_ret {
2528c2ecf20Sopenharmony_ci	local mod="$1"; shift
2538c2ecf20Sopenharmony_ci	local ret="$1"
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
2568c2ecf20Sopenharmony_ci	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	# Wait for sysfs value to hold ...
2598c2ecf20Sopenharmony_ci	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
2608c2ecf20Sopenharmony_ci		die "failed to set pre_patch_ret parameter for $mod module"
2618c2ecf20Sopenharmony_ci}
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_cifunction start_test {
2648c2ecf20Sopenharmony_ci	local test="$1"
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	save_dmesg
2678c2ecf20Sopenharmony_ci	echo -n "TEST: $test ... "
2688c2ecf20Sopenharmony_ci	log "===== TEST: $test ====="
2698c2ecf20Sopenharmony_ci}
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci# check_result() - verify dmesg output
2728c2ecf20Sopenharmony_ci#	TODO - better filter, out of order msgs, etc?
2738c2ecf20Sopenharmony_cifunction check_result {
2748c2ecf20Sopenharmony_ci	local expect="$*"
2758c2ecf20Sopenharmony_ci	local result
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	# Note: when comparing dmesg output, the kernel log timestamps
2788c2ecf20Sopenharmony_ci	# help differentiate repeated testing runs.  Remove them with a
2798c2ecf20Sopenharmony_ci	# post-comparison sed filter.
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \
2828c2ecf20Sopenharmony_ci		 grep -e 'livepatch:' -e 'test_klp' | \
2838c2ecf20Sopenharmony_ci		 grep -v '\(tainting\|taints\) kernel' | \
2848c2ecf20Sopenharmony_ci		 sed 's/^\[[ 0-9.]*\] //')
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	if [[ "$expect" == "$result" ]] ; then
2878c2ecf20Sopenharmony_ci		echo "ok"
2888c2ecf20Sopenharmony_ci	else
2898c2ecf20Sopenharmony_ci		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
2908c2ecf20Sopenharmony_ci		die "livepatch kselftest(s) failed"
2918c2ecf20Sopenharmony_ci	fi
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	cleanup_dmesg_file
2948c2ecf20Sopenharmony_ci}
295