2 # SPDX-License-Identifier: GPL-2.0
3 # Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
5 # Shell functions for the rest of the scripts.
8 RETRY_INTERVAL=".1" # seconds
9 KLP_SYSFS_DIR="/sys/kernel/livepatch"
11 # Kselftest framework requirement - SKIP code is 4
14 # log(msg) - write message to kernel log
15 # msg - insightful words
20 # skip(msg) - testing can't proceed
31 if [ $uid -ne 0 ]; then
32 echo "skip all tests: must be run as root" >&2
37 # Check if we can compile the modules before loading them
39 if [ -z "$KDIR" ]; then
40 KDIR="/lib/modules/$(uname -r)/build"
43 if [ ! -d "$KDIR" ]; then
44 echo "skip all tests: KDIR ($KDIR) not available to compile modules."
49 # die(msg) - game over, man
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)
63 function pop_config() {
64 if [[ -n "$DYNAMIC_DEBUG" ]]; then
65 echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control
67 if [[ -n "$FTRACE_ENABLED" ]]; then
68 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
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
79 function set_ftrace_enabled() {
81 if [[ "$1" == "--fail" ]] ; then
86 local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)
87 local result=$(sysctl --values kernel.ftrace_enabled)
89 if [[ "$result" != "$1" ]] ; then
90 if [[ $can_fail -eq 1 ]] ; then
91 echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg
95 skip "failed to set kernel.ftrace_enabled = $1"
98 echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
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() {
115 trap cleanup EXIT INT TERM HUP
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() {
125 eval "$cmd" && return 0
126 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1
127 sleep $RETRY_INTERVAL
131 function is_livepatch_mod() {
134 if [[ ! -f "test_modules/$mod.ko" ]]; then
135 die "Can't find \"test_modules/$mod.ko\", try \"make\""
138 if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
145 function __load_mod() {
146 local mod="$1"; shift
148 local msg="% insmod test_modules/$mod.ko $*"
150 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)
151 if [[ "$ret" != "" ]]; then
155 # Wait for module in sysfs ...
156 loop_until '[[ -e "/sys/module/$mod" ]]' ||
157 die "failed to load module $mod"
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
167 is_livepatch_mod "$mod" &&
168 die "use load_lp() to load the livepatch module $mod"
170 __load_mod "$mod" "$@"
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
180 is_livepatch_mod "$mod" ||
181 die "module $mod is not a livepatch"
183 __load_mod "$mod" "$@"
185 # Wait for livepatch in sysfs ...
186 loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
187 die "failed to load module $mod (sysfs)"
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
194 local mod="$1"; shift
196 load_lp_nowait "$mod" "$@"
198 # Wait until the transition finishes ...
199 loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
200 die "failed to complete transition"
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
209 local msg="% insmod test_modules/$mod.ko $*"
211 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)
212 if [[ "$ret" == "" ]]; then
213 die "$mod unexpectedly loaded"
218 # unload_mod(modname) - unload a kernel module
219 # modname - module name to unload
220 function unload_mod() {
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)"
228 ret=$(rmmod "$mod" 2>&1)
229 if [[ "$ret" != "" ]]; then
233 # Wait for module in sysfs ...
234 loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
235 die "failed to unload module $mod (/sys/module)"
238 # unload_lp(modname) - unload a kernel module with a livepatch
239 # modname - module name to unload
240 function unload_lp() {
244 # disable_lp(modname) - disable a livepatch
245 # modname - module name to unload
246 function disable_lp() {
249 log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
250 echo 0 > /sys/kernel/livepatch/"$mod"/enabled
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"
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
265 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
266 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
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"
273 function start_test {
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")
285 echo -n "TEST: $test ... "
286 log "===== TEST: $test ====="
289 # check_result() - verify dmesg output
290 # TODO - better filter, out of order msgs, etc?
291 function check_result {
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.]*\] //')
304 if [[ "$expect" == "$result" ]] ; then
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"
310 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
311 die "livepatch kselftest(s) failed"
315 # check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs
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
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"
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
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"