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