1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3# Copyright (C) 2018 Joe Lawrence <[email protected]> 4 5# Shell functions for the rest of the scripts. 6 7MAX_RETRIES=600 8RETRY_INTERVAL=".1" # seconds 9 10# Kselftest framework requirement - SKIP code is 4 11ksft_skip=4 12 13# log(msg) - write message to kernel log 14# msg - insightful words 15function log() { 16 echo "$1" > /dev/kmsg 17} 18 19# skip(msg) - testing can't proceed 20# msg - explanation 21function skip() { 22 log "SKIP: $1" 23 echo "SKIP: $1" >&2 24 exit $ksft_skip 25} 26 27# root test 28function is_root() { 29 uid=$(id -u) 30 if [ $uid -ne 0 ]; then 31 echo "skip all tests: must be run as root" >&2 32 exit $ksft_skip 33 fi 34} 35 36# die(msg) - game over, man 37# msg - dying words 38function die() { 39 log "ERROR: $1" 40 echo "ERROR: $1" >&2 41 exit 1 42} 43 44# save existing dmesg so we can detect new content 45function save_dmesg() { 46 SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX) 47 dmesg > "$SAVED_DMESG" 48} 49 50# cleanup temporary dmesg file from save_dmesg() 51function cleanup_dmesg_file() { 52 rm -f "$SAVED_DMESG" 53} 54 55function push_config() { 56 DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \ 57 awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}') 58 FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled) 59} 60 61function pop_config() { 62 if [[ -n "$DYNAMIC_DEBUG" ]]; then 63 echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control 64 fi 65 if [[ -n "$FTRACE_ENABLED" ]]; then 66 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null 67 fi 68} 69 70function set_dynamic_debug() { 71 cat <<-EOF > /sys/kernel/debug/dynamic_debug/control 72 file kernel/livepatch/* +p 73 func klp_try_switch_task -p 74 EOF 75} 76 77function set_ftrace_enabled() { 78 result=$(sysctl kernel.ftrace_enabled="$1" 2>&1 | paste --serial --delimiters=' ') 79 echo "livepatch: $result" > /dev/kmsg 80} 81 82function cleanup() { 83 pop_config 84 cleanup_dmesg_file 85} 86 87# setup_config - save the current config and set a script exit trap that 88# restores the original config. Setup the dynamic debug 89# for verbose livepatching output and turn on 90# the ftrace_enabled sysctl. 91function setup_config() { 92 is_root 93 push_config 94 set_dynamic_debug 95 set_ftrace_enabled 1 96 trap cleanup EXIT INT TERM HUP 97} 98 99# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 100# sleep $RETRY_INTERVAL between attempts 101# cmd - command and its arguments to run 102function loop_until() { 103 local cmd="$*" 104 local i=0 105 while true; do 106 eval "$cmd" && return 0 107 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 108 sleep $RETRY_INTERVAL 109 done 110} 111 112function assert_mod() { 113 local mod="$1" 114 115 modprobe --dry-run "$mod" &>/dev/null 116} 117 118function is_livepatch_mod() { 119 local mod="$1" 120 121 if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 122 return 0 123 fi 124 125 return 1 126} 127 128function __load_mod() { 129 local mod="$1"; shift 130 131 local msg="% modprobe $mod $*" 132 log "${msg%% }" 133 ret=$(modprobe "$mod" "$@" 2>&1) 134 if [[ "$ret" != "" ]]; then 135 die "$ret" 136 fi 137 138 # Wait for module in sysfs ... 139 loop_until '[[ -e "/sys/module/$mod" ]]' || 140 die "failed to load module $mod" 141} 142 143 144# load_mod(modname, params) - load a kernel module 145# modname - module name to load 146# params - module parameters to pass to modprobe 147function load_mod() { 148 local mod="$1"; shift 149 150 assert_mod "$mod" || 151 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 152 153 is_livepatch_mod "$mod" && 154 die "use load_lp() to load the livepatch module $mod" 155 156 __load_mod "$mod" "$@" 157} 158 159# load_lp_nowait(modname, params) - load a kernel module with a livepatch 160# but do not wait on until the transition finishes 161# modname - module name to load 162# params - module parameters to pass to modprobe 163function load_lp_nowait() { 164 local mod="$1"; shift 165 166 assert_mod "$mod" || 167 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 168 169 is_livepatch_mod "$mod" || 170 die "module $mod is not a livepatch" 171 172 __load_mod "$mod" "$@" 173 174 # Wait for livepatch in sysfs ... 175 loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' || 176 die "failed to load module $mod (sysfs)" 177} 178 179# load_lp(modname, params) - load a kernel module with a livepatch 180# modname - module name to load 181# params - module parameters to pass to modprobe 182function load_lp() { 183 local mod="$1"; shift 184 185 load_lp_nowait "$mod" "$@" 186 187 # Wait until the transition finishes ... 188 loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' || 189 die "failed to complete transition" 190} 191 192# load_failing_mod(modname, params) - load a kernel module, expect to fail 193# modname - module name to load 194# params - module parameters to pass to modprobe 195function load_failing_mod() { 196 local mod="$1"; shift 197 198 local msg="% modprobe $mod $*" 199 log "${msg%% }" 200 ret=$(modprobe "$mod" "$@" 2>&1) 201 if [[ "$ret" == "" ]]; then 202 die "$mod unexpectedly loaded" 203 fi 204 log "$ret" 205} 206 207# unload_mod(modname) - unload a kernel module 208# modname - module name to unload 209function unload_mod() { 210 local mod="$1" 211 212 # Wait for module reference count to clear ... 213 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 214 die "failed to unload module $mod (refcnt)" 215 216 log "% rmmod $mod" 217 ret=$(rmmod "$mod" 2>&1) 218 if [[ "$ret" != "" ]]; then 219 die "$ret" 220 fi 221 222 # Wait for module in sysfs ... 223 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 224 die "failed to unload module $mod (/sys/module)" 225} 226 227# unload_lp(modname) - unload a kernel module with a livepatch 228# modname - module name to unload 229function unload_lp() { 230 unload_mod "$1" 231} 232 233# disable_lp(modname) - disable a livepatch 234# modname - module name to unload 235function disable_lp() { 236 local mod="$1" 237 238 log "% echo 0 > /sys/kernel/livepatch/$mod/enabled" 239 echo 0 > /sys/kernel/livepatch/"$mod"/enabled 240 241 # Wait until the transition finishes and the livepatch gets 242 # removed from sysfs... 243 loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' || 244 die "failed to disable livepatch $mod" 245} 246 247# set_pre_patch_ret(modname, pre_patch_ret) 248# modname - module name to set 249# pre_patch_ret - new pre_patch_ret value 250function set_pre_patch_ret { 251 local mod="$1"; shift 252 local ret="$1" 253 254 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 255 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 256 257 # Wait for sysfs value to hold ... 258 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 259 die "failed to set pre_patch_ret parameter for $mod module" 260} 261 262function start_test { 263 local test="$1" 264 265 save_dmesg 266 echo -n "TEST: $test ... " 267} 268 269# check_result() - verify dmesg output 270# TODO - better filter, out of order msgs, etc? 271function check_result { 272 local expect="$*" 273 local result 274 275 # Note: when comparing dmesg output, the kernel log timestamps 276 # help differentiate repeated testing runs. Remove them with a 277 # post-comparison sed filter. 278 279 result=$(dmesg | diff --changed-group-format='%>' --unchanged-group-format='' "$SAVED_DMESG" - | \ 280 grep -v 'tainting' | grep -e 'livepatch:' -e 'test_klp' | \ 281 sed 's/^\[[ 0-9.]*\] //') 282 283 if [[ "$expect" == "$result" ]] ; then 284 echo "ok" 285 else 286 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 287 die "livepatch kselftest(s) failed" 288 fi 289 290 cleanup_dmesg_file 291} 292