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