1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
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	local can_fail=0
79	if [[ "$1" == "--fail" ]] ; then
80		can_fail=1
81		shift
82	fi
83
84	local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)
85	local result=$(sysctl --values kernel.ftrace_enabled)
86
87	if [[ "$result" != "$1" ]] ; then
88		if [[ $can_fail -eq 1 ]] ; then
89			echo "livepatch: $err" > /dev/kmsg
90			return
91		fi
92
93		skip "failed to set kernel.ftrace_enabled = $1"
94	fi
95
96	echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
97}
98
99function cleanup() {
100	pop_config
101	cleanup_dmesg_file
102}
103
104# setup_config - save the current config and set a script exit trap that
105#		 restores the original config.  Setup the dynamic debug
106#		 for verbose livepatching output and turn on
107#		 the ftrace_enabled sysctl.
108function setup_config() {
109	is_root
110	push_config
111	set_dynamic_debug
112	set_ftrace_enabled 1
113	trap cleanup EXIT INT TERM HUP
114}
115
116# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
117#		    sleep $RETRY_INTERVAL between attempts
118#	cmd - command and its arguments to run
119function loop_until() {
120	local cmd="$*"
121	local i=0
122	while true; do
123		eval "$cmd" && return 0
124		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
125		sleep $RETRY_INTERVAL
126	done
127}
128
129function assert_mod() {
130	local mod="$1"
131
132	modprobe --dry-run "$mod" &>/dev/null
133}
134
135function is_livepatch_mod() {
136	local mod="$1"
137
138	if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
139		return 0
140	fi
141
142	return 1
143}
144
145function __load_mod() {
146	local mod="$1"; shift
147
148	local msg="% modprobe $mod $*"
149	log "${msg%% }"
150	ret=$(modprobe "$mod" "$@" 2>&1)
151	if [[ "$ret" != "" ]]; then
152		die "$ret"
153	fi
154
155	# Wait for module in sysfs ...
156	loop_until '[[ -e "/sys/module/$mod" ]]' ||
157		die "failed to load module $mod"
158}
159
160
161# load_mod(modname, params) - load a kernel module
162#	modname - module name to load
163#	params  - module parameters to pass to modprobe
164function load_mod() {
165	local mod="$1"; shift
166
167	assert_mod "$mod" ||
168		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
169
170	is_livepatch_mod "$mod" &&
171		die "use load_lp() to load the livepatch module $mod"
172
173	__load_mod "$mod" "$@"
174}
175
176# load_lp_nowait(modname, params) - load a kernel module with a livepatch
177#			but do not wait on until the transition finishes
178#	modname - module name to load
179#	params  - module parameters to pass to modprobe
180function load_lp_nowait() {
181	local mod="$1"; shift
182
183	assert_mod "$mod" ||
184		skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
185
186	is_livepatch_mod "$mod" ||
187		die "module $mod is not a livepatch"
188
189	__load_mod "$mod" "$@"
190
191	# Wait for livepatch in sysfs ...
192	loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
193		die "failed to load module $mod (sysfs)"
194}
195
196# load_lp(modname, params) - load a kernel module with a livepatch
197#	modname - module name to load
198#	params  - module parameters to pass to modprobe
199function load_lp() {
200	local mod="$1"; shift
201
202	load_lp_nowait "$mod" "$@"
203
204	# Wait until the transition finishes ...
205	loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
206		die "failed to complete transition"
207}
208
209# load_failing_mod(modname, params) - load a kernel module, expect to fail
210#	modname - module name to load
211#	params  - module parameters to pass to modprobe
212function load_failing_mod() {
213	local mod="$1"; shift
214
215	local msg="% modprobe $mod $*"
216	log "${msg%% }"
217	ret=$(modprobe "$mod" "$@" 2>&1)
218	if [[ "$ret" == "" ]]; then
219		die "$mod unexpectedly loaded"
220	fi
221	log "$ret"
222}
223
224# unload_mod(modname) - unload a kernel module
225#	modname - module name to unload
226function unload_mod() {
227	local mod="$1"
228
229	# Wait for module reference count to clear ...
230	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
231		die "failed to unload module $mod (refcnt)"
232
233	log "% rmmod $mod"
234	ret=$(rmmod "$mod" 2>&1)
235	if [[ "$ret" != "" ]]; then
236		die "$ret"
237	fi
238
239	# Wait for module in sysfs ...
240	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
241		die "failed to unload module $mod (/sys/module)"
242}
243
244# unload_lp(modname) - unload a kernel module with a livepatch
245#	modname - module name to unload
246function unload_lp() {
247	unload_mod "$1"
248}
249
250# disable_lp(modname) - disable a livepatch
251#	modname - module name to unload
252function disable_lp() {
253	local mod="$1"
254
255	log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
256	echo 0 > /sys/kernel/livepatch/"$mod"/enabled
257
258	# Wait until the transition finishes and the livepatch gets
259	# removed from sysfs...
260	loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
261		die "failed to disable livepatch $mod"
262}
263
264# set_pre_patch_ret(modname, pre_patch_ret)
265#	modname - module name to set
266#	pre_patch_ret - new pre_patch_ret value
267function set_pre_patch_ret {
268	local mod="$1"; shift
269	local ret="$1"
270
271	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
272	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
273
274	# Wait for sysfs value to hold ...
275	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
276		die "failed to set pre_patch_ret parameter for $mod module"
277}
278
279function start_test {
280	local test="$1"
281
282	save_dmesg
283	echo -n "TEST: $test ... "
284	log "===== TEST: $test ====="
285}
286
287# check_result() - verify dmesg output
288#	TODO - better filter, out of order msgs, etc?
289function check_result {
290	local expect="$*"
291	local result
292
293	# Note: when comparing dmesg output, the kernel log timestamps
294	# help differentiate repeated testing runs.  Remove them with a
295	# post-comparison sed filter.
296
297	result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \
298		 grep -e 'livepatch:' -e 'test_klp' | \
299		 grep -v '\(tainting\|taints\) kernel' | \
300		 sed 's/^\[[ 0-9.]*\] //')
301
302	if [[ "$expect" == "$result" ]] ; then
303		echo "ok"
304	else
305		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
306		die "livepatch kselftest(s) failed"
307	fi
308
309	cleanup_dmesg_file
310}
311