1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3# Copyright (C) 2018 Joe Lawrence <[email protected]>
4
5# Shell functions for the rest of the scripts.
6
7MAX_RETRIES=600
8RETRY_INTERVAL=".1"	# seconds
9
10# Kselftest framework requirement - SKIP code is 4
11ksft_skip=4
12
13# log(msg) - write message to kernel log
14#	msg - insightful words
15function log() {
16	echo "$1" > /dev/kmsg
17}
18
19# skip(msg) - testing can't proceed
20#	msg - explanation
21function skip() {
22	log "SKIP: $1"
23	echo "SKIP: $1" >&2
24	exit $ksft_skip
25}
26
27# root test
28function is_root() {
29	uid=$(id -u)
30	if [ $uid -ne 0 ]; then
31		echo "skip all tests: must be run as root" >&2
32		exit $ksft_skip
33	fi
34}
35
36# die(msg) - game over, man
37#	msg - dying words
38function die() {
39	log "ERROR: $1"
40	echo "ERROR: $1" >&2
41	exit 1
42}
43
44# save existing dmesg so we can detect new content
45function save_dmesg() {
46	SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX)
47	dmesg > "$SAVED_DMESG"
48}
49
50# cleanup temporary dmesg file from save_dmesg()
51function cleanup_dmesg_file() {
52	rm -f "$SAVED_DMESG"
53}
54
55function push_config() {
56	DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \
57			awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')
58	FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)
59}
60
61function pop_config() {
62	if [[ -n "$DYNAMIC_DEBUG" ]]; then
63		echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control
64	fi
65	if [[ -n "$FTRACE_ENABLED" ]]; then
66		sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
67	fi
68}
69
70function set_dynamic_debug() {
71        cat <<-EOF > /sys/kernel/debug/dynamic_debug/control
72		file kernel/livepatch/* +p
73		func klp_try_switch_task -p
74		EOF
75}
76
77function set_ftrace_enabled() {
78	result=$(sysctl kernel.ftrace_enabled="$1" 2>&1 | paste --serial --delimiters=' ')
79	echo "livepatch: $result" > /dev/kmsg
80}
81
82function cleanup() {
83	pop_config
84	cleanup_dmesg_file
85}
86
87# setup_config - save the current config and set a script exit trap that
88#		 restores the original config.  Setup the dynamic debug
89#		 for verbose livepatching output and turn on
90#		 the ftrace_enabled sysctl.
91function setup_config() {
92	is_root
93	push_config
94	set_dynamic_debug
95	set_ftrace_enabled 1
96	trap cleanup EXIT INT TERM HUP
97}
98
99# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
100#		    sleep $RETRY_INTERVAL between attempts
101#	cmd - command and its arguments to run
102function loop_until() {
103	local cmd="$*"
104	local i=0
105	while true; do
106		eval "$cmd" && return 0
107		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
108		sleep $RETRY_INTERVAL
109	done
110}
111
112function assert_mod() {
113	local mod="$1"
114
115	modprobe --dry-run "$mod" &>/dev/null
116}
117
118function is_livepatch_mod() {
119	local mod="$1"
120
121	if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
122		return 0
123	fi
124
125	return 1
126}
127
128function __load_mod() {
129	local mod="$1"; shift
130
131	local msg="% modprobe $mod $*"
132	log "${msg%% }"
133	ret=$(modprobe "$mod" "$@" 2>&1)
134	if [[ "$ret" != "" ]]; then
135		die "$ret"
136	fi
137
138	# Wait for module in sysfs ...
139	loop_until '[[ -e "/sys/module/$mod" ]]' ||
140		die "failed to load module $mod"
141}
142
143
144# load_mod(modname, params) - load a kernel module
145#	modname - module name to load
146#	params  - module parameters to pass to modprobe
147function load_mod() {
148	local mod="$1"; shift
149
150	assert_mod "$mod" ||
151		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
152
153	is_livepatch_mod "$mod" &&
154		die "use load_lp() to load the livepatch module $mod"
155
156	__load_mod "$mod" "$@"
157}
158
159# load_lp_nowait(modname, params) - load a kernel module with a livepatch
160#			but do not wait on until the transition finishes
161#	modname - module name to load
162#	params  - module parameters to pass to modprobe
163function load_lp_nowait() {
164	local mod="$1"; shift
165
166	assert_mod "$mod" ||
167		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
168
169	is_livepatch_mod "$mod" ||
170		die "module $mod is not a livepatch"
171
172	__load_mod "$mod" "$@"
173
174	# Wait for livepatch in sysfs ...
175	loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
176		die "failed to load module $mod (sysfs)"
177}
178
179# load_lp(modname, params) - load a kernel module with a livepatch
180#	modname - module name to load
181#	params  - module parameters to pass to modprobe
182function load_lp() {
183	local mod="$1"; shift
184
185	load_lp_nowait "$mod" "$@"
186
187	# Wait until the transition finishes ...
188	loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
189		die "failed to complete transition"
190}
191
192# load_failing_mod(modname, params) - load a kernel module, expect to fail
193#	modname - module name to load
194#	params  - module parameters to pass to modprobe
195function load_failing_mod() {
196	local mod="$1"; shift
197
198	local msg="% modprobe $mod $*"
199	log "${msg%% }"
200	ret=$(modprobe "$mod" "$@" 2>&1)
201	if [[ "$ret" == "" ]]; then
202		die "$mod unexpectedly loaded"
203	fi
204	log "$ret"
205}
206
207# unload_mod(modname) - unload a kernel module
208#	modname - module name to unload
209function unload_mod() {
210	local mod="$1"
211
212	# Wait for module reference count to clear ...
213	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
214		die "failed to unload module $mod (refcnt)"
215
216	log "% rmmod $mod"
217	ret=$(rmmod "$mod" 2>&1)
218	if [[ "$ret" != "" ]]; then
219		die "$ret"
220	fi
221
222	# Wait for module in sysfs ...
223	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
224		die "failed to unload module $mod (/sys/module)"
225}
226
227# unload_lp(modname) - unload a kernel module with a livepatch
228#	modname - module name to unload
229function unload_lp() {
230	unload_mod "$1"
231}
232
233# disable_lp(modname) - disable a livepatch
234#	modname - module name to unload
235function disable_lp() {
236	local mod="$1"
237
238	log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
239	echo 0 > /sys/kernel/livepatch/"$mod"/enabled
240
241	# Wait until the transition finishes and the livepatch gets
242	# removed from sysfs...
243	loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
244		die "failed to disable livepatch $mod"
245}
246
247# set_pre_patch_ret(modname, pre_patch_ret)
248#	modname - module name to set
249#	pre_patch_ret - new pre_patch_ret value
250function set_pre_patch_ret {
251	local mod="$1"; shift
252	local ret="$1"
253
254	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
255	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
256
257	# Wait for sysfs value to hold ...
258	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
259		die "failed to set pre_patch_ret parameter for $mod module"
260}
261
262function start_test {
263	local test="$1"
264
265	save_dmesg
266	echo -n "TEST: $test ... "
267}
268
269# check_result() - verify dmesg output
270#	TODO - better filter, out of order msgs, etc?
271function check_result {
272	local expect="$*"
273	local result
274
275	# Note: when comparing dmesg output, the kernel log timestamps
276	# help differentiate repeated testing runs.  Remove them with a
277	# post-comparison sed filter.
278
279	result=$(dmesg | diff --changed-group-format='%>' --unchanged-group-format='' "$SAVED_DMESG" - | \
280		 grep -v 'tainting' | grep -e 'livepatch:' -e 'test_klp' | \
281		 sed 's/^\[[ 0-9.]*\] //')
282
283	if [[ "$expect" == "$result" ]] ; then
284		echo "ok"
285	else
286		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
287		die "livepatch kselftest(s) failed"
288	fi
289
290	cleanup_dmesg_file
291}
292