xref: /linux/tools/testing/selftests/livepatch/functions.sh (revision 09005a63988521f74111fae344aecb3f63306168)
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
7export LC_ALL=C
8
9MAX_RETRIES=600
10RETRY_INTERVAL=".1"	# seconds
11SYSFS_KERNEL_DIR="/sys/kernel"
12SYSFS_KLP_DIR="$SYSFS_KERNEL_DIR/livepatch"
13SYSFS_DEBUG_DIR="$SYSFS_KERNEL_DIR/debug"
14SYSFS_KPROBES_DIR="$SYSFS_DEBUG_DIR/kprobes"
15if [[ -e /sys/kernel/tracing/trace ]]; then
16	SYSFS_TRACING_DIR="$SYSFS_KERNEL_DIR/tracing"
17else
18	SYSFS_TRACING_DIR="$SYSFS_DEBUG_DIR/tracing"
19fi
20
21# Kselftest framework requirement - SKIP code is 4
22ksft_skip=4
23
24# log(msg) - write message to kernel log
25#	msg - insightful words
26function log() {
27	echo "$1" > /dev/kmsg
28}
29
30# skip(msg) - testing can't proceed
31#	msg - explanation
32function skip() {
33	log "SKIP: $1"
34	echo "SKIP: $1" >&2
35	exit $ksft_skip
36}
37
38# root test
39function is_root() {
40	uid=$(id -u)
41	if [ $uid -ne 0 ]; then
42		echo "skip all tests: must be run as root" >&2
43		exit $ksft_skip
44	fi
45}
46
47# Check if we can compile the modules before loading them
48function has_kdir() {
49	if [ -z "$KDIR" ]; then
50		KDIR="/lib/modules/$(uname -r)/build"
51	fi
52
53	if [ ! -d "$KDIR" ]; then
54		echo "skip all tests: KDIR ($KDIR) not available to compile modules."
55		exit $ksft_skip
56	fi
57}
58
59# die(msg) - game over, man
60#	msg - dying words
61function die() {
62	log "ERROR: $1"
63	echo "ERROR: $1" >&2
64	exit 1
65}
66
67function push_config() {
68	DYNAMIC_DEBUG=$(grep '^kernel/livepatch' "$SYSFS_DEBUG_DIR/dynamic_debug/control" | \
69			awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')
70	FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)
71	KPROBE_ENABLED=$(cat "$SYSFS_KPROBES_DIR/enabled")
72	TRACING_ON=$(cat "$SYSFS_TRACING_DIR/tracing_on")
73	CURRENT_TRACER=$(cat "$SYSFS_TRACING_DIR/current_tracer")
74	FTRACE_FILTER=$(cat "$SYSFS_TRACING_DIR/set_ftrace_filter")
75}
76
77function pop_config() {
78	if [[ -n "$DYNAMIC_DEBUG" ]]; then
79		echo -n "$DYNAMIC_DEBUG" > "$SYSFS_DEBUG_DIR/dynamic_debug/control"
80	fi
81	if [[ -n "$FTRACE_ENABLED" ]]; then
82		sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
83	fi
84	if [[ -n "$KPROBE_ENABLED" ]]; then
85		echo "$KPROBE_ENABLED" > "$SYSFS_KPROBES_DIR/enabled"
86	fi
87	if [[ -n "$TRACING_ON" ]]; then
88		echo "$TRACING_ON" > "$SYSFS_TRACING_DIR/tracing_on"
89	fi
90	if [[ -n "$CURRENT_TRACER" ]]; then
91		echo "$CURRENT_TRACER" > "$SYSFS_TRACING_DIR/current_tracer"
92	fi
93	if [[ -n "$FTRACE_FILTER" ]]; then
94		echo "$FTRACE_FILTER" \
95			| sed -e "/#### all functions enabled ####/d" \
96			> "$SYSFS_TRACING_DIR/set_ftrace_filter"
97	fi
98}
99
100function set_dynamic_debug() {
101        cat <<-EOF > "$SYSFS_DEBUG_DIR/dynamic_debug/control"
102		file kernel/livepatch/* +p
103		func klp_try_switch_task -p
104		EOF
105}
106
107function set_ftrace_enabled() {
108	local can_fail=0
109	if [[ "$1" == "--fail" ]] ; then
110		can_fail=1
111		shift
112	fi
113
114	local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)
115	local result=$(sysctl --values kernel.ftrace_enabled)
116
117	if [[ "$result" != "$1" ]] ; then
118		if [[ $can_fail -eq 1 ]] ; then
119			echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg
120			return
121		fi
122
123		skip "failed to set kernel.ftrace_enabled = $1"
124	fi
125
126	echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
127}
128
129# ftrace_disable_supported() - probe whether kernel.ftrace_enabled=0
130#	can still disable ftrace on this kernel. Newer kernels deprecate
131#	the knob and always refuse the write with -EOPNOTSUPP.
132function ftrace_disable_supported() {
133	local orig result
134
135	orig=$(sysctl --values kernel.ftrace_enabled)
136	sysctl -q kernel.ftrace_enabled=0 &> /dev/null
137	result=$(sysctl --values kernel.ftrace_enabled)
138	sysctl -q "kernel.ftrace_enabled=$orig" &> /dev/null
139
140	[[ "$result" == "0" ]]
141}
142
143function cleanup() {
144	pop_config
145}
146
147# setup_config - save the current config and set a script exit trap that
148#		 restores the original config.  Setup the dynamic debug
149#		 for verbose livepatching output and turn on
150#		 the ftrace_enabled sysctl.
151function setup_config() {
152	is_root
153	has_kdir
154	push_config
155	set_dynamic_debug
156	set_ftrace_enabled 1
157	trap cleanup EXIT INT TERM HUP
158}
159
160# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
161#		    sleep $RETRY_INTERVAL between attempts
162#	cmd - command and its arguments to run
163function loop_until() {
164	local cmd="$*"
165	local i=0
166	while true; do
167		eval "$cmd" && return 0
168		[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
169		sleep $RETRY_INTERVAL
170	done
171}
172
173function is_livepatch_mod() {
174	local mod="$1"
175
176	if [[ ! -f "test_modules/$mod.ko" ]]; then
177		die "Can't find \"test_modules/$mod.ko\", try \"make\""
178	fi
179
180	if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
181		return 0
182	fi
183
184	return 1
185}
186
187function __load_mod() {
188	local mod="$1"; shift
189
190	local msg="% insmod test_modules/$mod.ko $*"
191	log "${msg%% }"
192	ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)
193	if [[ "$ret" != "" ]]; then
194		die "$ret"
195	fi
196
197	# Wait for module in sysfs ...
198	loop_until '[[ -e "/sys/module/$mod" ]]' ||
199		die "failed to load module $mod"
200}
201
202
203# load_mod(modname, params) - load a kernel module
204#	modname - module name to load
205#	params  - module parameters to pass to insmod
206function load_mod() {
207	local mod="$1"; shift
208
209	is_livepatch_mod "$mod" &&
210		die "use load_lp() to load the livepatch module $mod"
211
212	__load_mod "$mod" "$@"
213}
214
215# load_lp_nowait(modname, params) - load a kernel module with a livepatch
216#			but do not wait on until the transition finishes
217#	modname - module name to load
218#	params  - module parameters to pass to insmod
219function load_lp_nowait() {
220	local mod="$1"; shift
221
222	is_livepatch_mod "$mod" ||
223		die "module $mod is not a livepatch"
224
225	__load_mod "$mod" "$@"
226
227	# Wait for livepatch in sysfs ...
228	loop_until '[[ -e "$SYSFS_KLP_DIR/$mod" ]]' ||
229		die "failed to load module $mod (sysfs)"
230}
231
232# load_lp(modname, params) - load a kernel module with a livepatch
233#	modname - module name to load
234#	params  - module parameters to pass to insmod
235function load_lp() {
236	local mod="$1"; shift
237
238	load_lp_nowait "$mod" "$@"
239
240	# Wait until the transition finishes ...
241	loop_until 'grep -q '^0$' $SYSFS_KLP_DIR/$mod/transition' ||
242		die "failed to complete transition"
243}
244
245# load_failing_mod(modname, params) - load a kernel module, expect to fail
246#	modname - module name to load
247#	params  - module parameters to pass to insmod
248function load_failing_mod() {
249	local mod="$1"; shift
250
251	local msg="% insmod test_modules/$mod.ko $*"
252	log "${msg%% }"
253	ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)
254	if [[ "$ret" == "" ]]; then
255		die "$mod unexpectedly loaded"
256	fi
257	log "$ret"
258}
259
260# unload_mod(modname) - unload a kernel module
261#	modname - module name to unload
262function unload_mod() {
263	local mod="$1"
264
265	# Wait for module reference count to clear ...
266	loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
267		die "failed to unload module $mod (refcnt)"
268
269	log "% rmmod $mod"
270	ret=$(rmmod "$mod" 2>&1)
271	if [[ "$ret" != "" ]]; then
272		die "$ret"
273	fi
274
275	# Wait for module in sysfs ...
276	loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
277		die "failed to unload module $mod (/sys/module)"
278}
279
280# unload_lp(modname) - unload a kernel module with a livepatch
281#	modname - module name to unload
282function unload_lp() {
283	unload_mod "$1"
284}
285
286# disable_lp(modname) - disable a livepatch
287#	modname - module name to unload
288function disable_lp() {
289	local mod="$1"
290
291	log "% echo 0 > $SYSFS_KLP_DIR/$mod/enabled"
292	echo 0 > "$SYSFS_KLP_DIR/$mod/enabled"
293
294	# Wait until the transition finishes and the livepatch gets
295	# removed from sysfs...
296	loop_until '[[ ! -e "$SYSFS_KLP_DIR/$mod" ]]' ||
297		die "failed to disable livepatch $mod"
298}
299
300# set_pre_patch_ret(modname, pre_patch_ret)
301#	modname - module name to set
302#	pre_patch_ret - new pre_patch_ret value
303function set_pre_patch_ret {
304	local mod="$1"; shift
305	local ret="$1"
306
307	log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
308	echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
309
310	# Wait for sysfs value to hold ...
311	loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
312		die "failed to set pre_patch_ret parameter for $mod module"
313}
314
315function start_test {
316	local test="$1"
317
318	# Dump something unique into the dmesg log, then stash the entry
319	# in LAST_DMESG.  The check_result() function will use it to
320	# find new kernel messages since the test started.
321	local last_dmesg_msg="livepatch kselftest timestamp: $(date --rfc-3339=ns)"
322	log "$last_dmesg_msg"
323	loop_until 'dmesg | grep -q "$last_dmesg_msg"' ||
324		die "buffer busy? can't find canary dmesg message: $last_dmesg_msg"
325	LAST_DMESG=$(dmesg | grep "$last_dmesg_msg")
326
327	echo -n "TEST: $test ... "
328	log "===== TEST: $test ====="
329}
330
331# check_result() - verify dmesg output
332#	TODO - better filter, out of order msgs, etc?
333function check_result {
334	local expect="$*"
335	local result
336
337	# Test results include any new dmesg entry since LAST_DMESG, then:
338	# - include lines matching keywords
339	# - exclude lines matching keywords
340	# - filter out dmesg timestamp prefixes
341	result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \
342		 grep -e 'livepatch:' -e 'test_klp' | \
343		 grep -v '\(tainting\|taints\) kernel' | \
344		 sed 's/^\[[ 0-9.]*\] //' | \
345		 sed 's/^\[[ ]*[CT][0-9]*\] //')
346
347	if [[ "$expect" == "$result" ]] ; then
348		echo "ok"
349	elif [[ "$result" == "" ]] ; then
350		echo -e "not ok\n\nbuffer overrun? can't find canary dmesg entry: $LAST_DMESG\n"
351		die "livepatch kselftest(s) failed"
352	else
353		echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
354		die "livepatch kselftest(s) failed"
355	fi
356}
357
358# does_sysfs_exist(modname, attr) - check sysfs attribute existence
359#	modname - livepatch module creating the sysfs interface
360#	attr - attribute name to be checked
361function does_sysfs_exist() {
362	local mod="$1"; shift
363	local attr="$1"; shift
364
365	[[ -f "$SYSFS_KLP_DIR/$mod/$attr" ]]
366}
367
368# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs
369# path permissions
370#	modname - livepatch module creating the sysfs interface
371#	rel_path - relative path of the sysfs interface
372#	expected_rights - expected access rights
373function check_sysfs_rights() {
374	local mod="$1"; shift
375	local rel_path="$1"; shift
376	local expected_rights="$1"; shift
377
378	local path="$SYSFS_KLP_DIR/$mod/$rel_path"
379	local rights=$(/bin/stat --format '%A' "$path")
380	if test "$rights" != "$expected_rights" ; then
381		die "Unexpected access rights of $path: $expected_rights vs. $rights"
382	fi
383}
384
385# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value
386#	modname - livepatch module creating the sysfs interface
387#	rel_path - relative path of the sysfs interface
388#	expected_value - expected value read from the file
389function check_sysfs_value() {
390	local mod="$1"; shift
391	local rel_path="$1"; shift
392	local expected_value="$1"; shift
393
394	local path="$SYSFS_KLP_DIR/$mod/$rel_path"
395	local value=`cat $path`
396	if test "$value" != "$expected_value" ; then
397		die "Unexpected value in $path: $expected_value vs. $value"
398	fi
399}
400
401# cleanup_tracing() - stop and clean up function tracing
402function cleanup_tracing() {
403	echo 0 > "$SYSFS_TRACING_DIR/tracing_on"
404	echo "" > "$SYSFS_TRACING_DIR/set_ftrace_filter"
405	echo "nop" > "$SYSFS_TRACING_DIR/current_tracer"
406	echo "" > "$SYSFS_TRACING_DIR/trace"
407}
408
409# trace_function(function) - start tracing of a function
410#	function - to be traced function
411function trace_function() {
412	local function="$1"; shift
413
414	cleanup_tracing
415
416	echo "function" > "$SYSFS_TRACING_DIR/current_tracer"
417	echo "$function" > "$SYSFS_TRACING_DIR/set_ftrace_filter"
418	echo 1 > "$SYSFS_TRACING_DIR/tracing_on"
419}
420
421# check_traced_functions(functions...) - check whether each function appeared in the trace log
422#	functions - list of functions to be checked
423function check_traced_functions() {
424	local function
425
426	for function in "$@"; do
427		if ! grep -Fwq "$function" "$SYSFS_TRACING_DIR/trace" ; then
428			die "Function ($function) did not appear in the trace"
429		fi
430	done
431
432	cleanup_tracing
433}
434