Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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}