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