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# Check if we can compile the modules before loading them
38function has_kdir() {
39	if [ -z "$KDIR" ]; then
40		KDIR="/lib/modules/$(uname -r)/build"
41	fi
42
43	if [ ! -d "$KDIR" ]; then
44		echo "skip all tests: KDIR ($KDIR) not available to compile modules."
45		exit $ksft_skip
46	fi
47}
48
49# die(msg) - game over, man
50#	msg - dying words
51function die() {
52	log "ERROR: $1"
53	echo "ERROR: $1" >&2
54	exit 1
55}
56
57function push_config() {
58	DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \
59			awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')
60	FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)
61}
62
63function pop_config() {
64	if [[ -n "$DYNAMIC_DEBUG" ]]; then
65		echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control
66	fi
67	if [[ -n "$FTRACE_ENABLED" ]]; then
68		sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
69	fi
70}
71
72function set_dynamic_debug() {
73        cat <<-EOF > /sys/kernel/debug/dynamic_debug/control
74		file kernel/livepatch/* +p
75		func klp_try_switch_task -p
76		EOF
77}
78
79function set_ftrace_enabled() {
80	local can_fail=0
81	if [[ "$1" == "--fail" ]] ; then
82		can_fail=1
83		shift
84	fi
85
86	local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)
87	local result=$(sysctl --values kernel.ftrace_enabled)
88
89	if [[ "$result" != "$1" ]] ; then
90		if [[ $can_fail -eq 1 ]] ; then
91			echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg
92			return
93		fi
94
95		skip "failed to set kernel.ftrace_enabled = $1"
96	fi
97
98	echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
99}
100
101function cleanup() {
102	pop_config
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	has_kdir
112	push_config
113	set_dynamic_debug
114	set_ftrace_enabled 1
115	trap cleanup EXIT INT TERM HUP
116}
117
118# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
119#		    sleep $RETRY_INTERVAL between attempts
120#	cmd - command and its arguments to run
121function loop_until() {
122	local cmd="$*"
123	local i=0
124	while true; do
125		eval "$cmd" && return 0
126		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
127		sleep $RETRY_INTERVAL
128	done
129}
130
131function is_livepatch_mod() {
132	local mod="$1"
133
134	if [[ ! -f "test_modules/$mod.ko" ]]; then
135		die "Can't find \"test_modules/$mod.ko\", try \"make\""
136	fi
137
138	if [[ $(modinfo "test_modules/$mod.ko" | 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="% insmod test_modules/$mod.ko $*"
149	log "${msg%% }"
150	ret=$(insmod "test_modules/$mod.ko" "$@" 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 insmod
164function load_mod() {
165	local mod="$1"; shift
166
167	is_livepatch_mod "$mod" &&
168		die "use load_lp() to load the livepatch module $mod"
169
170	__load_mod "$mod" "$@"
171}
172
173# load_lp_nowait(modname, params) - load a kernel module with a livepatch
174#			but do not wait on until the transition finishes
175#	modname - module name to load
176#	params  - module parameters to pass to insmod
177function load_lp_nowait() {
178	local mod="$1"; shift
179
180	is_livepatch_mod "$mod" ||
181		die "module $mod is not a livepatch"
182
183	__load_mod "$mod" "$@"
184
185	# Wait for livepatch in sysfs ...
186	loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
187		die "failed to load module $mod (sysfs)"
188}
189
190# load_lp(modname, params) - load a kernel module with a livepatch
191#	modname - module name to load
192#	params  - module parameters to pass to insmod
193function load_lp() {
194	local mod="$1"; shift
195
196	load_lp_nowait "$mod" "$@"
197
198	# Wait until the transition finishes ...
199	loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
200		die "failed to complete transition"
201}
202
203# load_failing_mod(modname, params) - load a kernel module, expect to fail
204#	modname - module name to load
205#	params  - module parameters to pass to insmod
206function load_failing_mod() {
207	local mod="$1"; shift
208
209	local msg="% insmod test_modules/$mod.ko $*"
210	log "${msg%% }"
211	ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)
212	if [[ "$ret" == "" ]]; then
213		die "$mod unexpectedly loaded"
214	fi
215	log "$ret"
216}
217
218# unload_mod(modname) - unload a kernel module
219#	modname - module name to unload
220function unload_mod() {
221	local mod="$1"
222
223	# Wait for module reference count to clear ...
224	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
225		die "failed to unload module $mod (refcnt)"
226
227	log "% rmmod $mod"
228	ret=$(rmmod "$mod" 2>&1)
229	if [[ "$ret" != "" ]]; then
230		die "$ret"
231	fi
232
233	# Wait for module in sysfs ...
234	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
235		die "failed to unload module $mod (/sys/module)"
236}
237
238# unload_lp(modname) - unload a kernel module with a livepatch
239#	modname - module name to unload
240function unload_lp() {
241	unload_mod "$1"
242}
243
244# disable_lp(modname) - disable a livepatch
245#	modname - module name to unload
246function disable_lp() {
247	local mod="$1"
248
249	log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
250	echo 0 > /sys/kernel/livepatch/"$mod"/enabled
251
252	# Wait until the transition finishes and the livepatch gets
253	# removed from sysfs...
254	loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
255		die "failed to disable livepatch $mod"
256}
257
258# set_pre_patch_ret(modname, pre_patch_ret)
259#	modname - module name to set
260#	pre_patch_ret - new pre_patch_ret value
261function set_pre_patch_ret {
262	local mod="$1"; shift
263	local ret="$1"
264
265	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
266	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
267
268	# Wait for sysfs value to hold ...
269	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
270		die "failed to set pre_patch_ret parameter for $mod module"
271}
272
273function start_test {
274	local test="$1"
275
276	# Dump something unique into the dmesg log, then stash the entry
277	# in LAST_DMESG.  The check_result() function will use it to
278	# find new kernel messages since the test started.
279	local last_dmesg_msg="livepatch kselftest timestamp: $(date --rfc-3339=ns)"
280	log "$last_dmesg_msg"
281	loop_until 'dmesg | grep -q "$last_dmesg_msg"' ||
282		die "buffer busy? can't find canary dmesg message: $last_dmesg_msg"
283	LAST_DMESG=$(dmesg | grep "$last_dmesg_msg")
284
285	echo -n "TEST: $test ... "
286	log "===== TEST: $test ====="
287}
288
289# check_result() - verify dmesg output
290#	TODO - better filter, out of order msgs, etc?
291function check_result {
292	local expect="$*"
293	local result
294
295	# Test results include any new dmesg entry since LAST_DMESG, then:
296	# - include lines matching keywords
297	# - exclude lines matching keywords
298	# - filter out dmesg timestamp prefixes
299	result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \
300		 grep -e 'livepatch:' -e 'test_klp' | \
301		 grep -v '\(tainting\|taints\) kernel' | \
302		 sed 's/^\[[ 0-9.]*\] //')
303
304	if [[ "$expect" == "$result" ]] ; then
305		echo "ok"
306	elif [[ "$result" == "" ]] ; then
307		echo -e "not ok\n\nbuffer overrun? can't find canary dmesg entry: $LAST_DMESG\n"
308		die "livepatch kselftest(s) failed"
309	else
310		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
311		die "livepatch kselftest(s) failed"
312	fi
313}
314
315# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs
316# path permissions
317#	modname - livepatch module creating the sysfs interface
318#	rel_path - relative path of the sysfs interface
319#	expected_rights - expected access rights
320function check_sysfs_rights() {
321	local mod="$1"; shift
322	local rel_path="$1"; shift
323	local expected_rights="$1"; shift
324
325	local path="$KLP_SYSFS_DIR/$mod/$rel_path"
326	local rights=$(/bin/stat --format '%A' "$path")
327	if test "$rights" != "$expected_rights" ; then
328		die "Unexpected access rights of $path: $expected_rights vs. $rights"
329	fi
330}
331
332# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value
333#	modname - livepatch module creating the sysfs interface
334#	rel_path - relative path of the sysfs interface
335#	expected_value - expected value read from the file
336function check_sysfs_value() {
337	local mod="$1"; shift
338	local rel_path="$1"; shift
339	local expected_value="$1"; shift
340
341	local path="$KLP_SYSFS_DIR/$mod/$rel_path"
342	local value=`cat $path`
343	if test "$value" != "$expected_value" ; then
344		die "Unexpected value in $path: $expected_value vs. $value"
345	fi
346}
347