Merge tag 'x86-asm-2024-03-11' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
[linux-2.6-block.git] / tools / testing / selftests / livepatch / functions.sh
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
7 MAX_RETRIES=600
8 RETRY_INTERVAL=".1"     # seconds
9 KLP_SYSFS_DIR="/sys/kernel/livepatch"
10
11 # Kselftest framework requirement - SKIP code is 4
12 ksft_skip=4
13
14 # log(msg) - write message to kernel log
15 #       msg - insightful words
16 function log() {
17         echo "$1" > /dev/kmsg
18 }
19
20 # skip(msg) - testing can't proceed
21 #       msg - explanation
22 function skip() {
23         log "SKIP: $1"
24         echo "SKIP: $1" >&2
25         exit $ksft_skip
26 }
27
28 # root test
29 function 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
38 function 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
51 function die() {
52         log "ERROR: $1"
53         echo "ERROR: $1" >&2
54         exit 1
55 }
56
57 function 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
63 function 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
72 function 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
79 function 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
101 function 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.
109 function 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
121 function 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
131 function 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
145 function __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
164 function 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
177 function 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
193 function 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
206 function 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
220 function 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
240 function unload_lp() {
241         unload_mod "$1"
242 }
243
244 # disable_lp(modname) - disable a livepatch
245 #       modname - module name to unload
246 function 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
261 function 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
273 function 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?
291 function 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
320 function 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
336 function 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 }