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" 13 14# Kselftest framework requirement - SKIP code is 4 15ksft_skip=4 16 17# log(msg) - write message to kernel log 18# msg - insightful words 19function log() { 20 echo "$1" > /dev/kmsg 21} 22 23# skip(msg) - testing can't proceed 24# msg - explanation 25function skip() { 26 log "SKIP: $1" 27 echo "SKIP: $1" >&2 28 exit $ksft_skip 29} 30 31# root test 32function is_root() { 33 uid=$(id -u) 34 if [ $uid -ne 0 ]; then 35 echo "skip all tests: must be run as root" >&2 36 exit $ksft_skip 37 fi 38} 39 40# Check if we can compile the modules before loading them 41function has_kdir() { 42 if [ -z "$KDIR" ]; then 43 KDIR="/lib/modules/$(uname -r)/build" 44 fi 45 46 if [ ! -d "$KDIR" ]; then 47 echo "skip all tests: KDIR ($KDIR) not available to compile modules." 48 exit $ksft_skip 49 fi 50} 51 52# die(msg) - game over, man 53# msg - dying words 54function die() { 55 log "ERROR: $1" 56 echo "ERROR: $1" >&2 57 exit 1 58} 59 60function push_config() { 61 DYNAMIC_DEBUG=$(grep '^kernel/livepatch' "$SYSFS_DEBUG_DIR/dynamic_debug/control" | \ 62 awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}') 63 FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled) 64 KPROBE_ENABLED=$(cat "$SYSFS_KPROBES_DIR/enabled") 65} 66 67function pop_config() { 68 if [[ -n "$DYNAMIC_DEBUG" ]]; then 69 echo -n "$DYNAMIC_DEBUG" > "$SYSFS_DEBUG_DIR/dynamic_debug/control" 70 fi 71 if [[ -n "$FTRACE_ENABLED" ]]; then 72 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null 73 fi 74 if [[ -n "$KPROBE_ENABLED" ]]; then 75 echo "$KPROBE_ENABLED" > "$SYSFS_KPROBES_DIR/enabled" 76 fi 77} 78 79function set_dynamic_debug() { 80 cat <<-EOF > "$SYSFS_DEBUG_DIR/dynamic_debug/control" 81 file kernel/livepatch/* +p 82 func klp_try_switch_task -p 83 EOF 84} 85 86function set_ftrace_enabled() { 87 local can_fail=0 88 if [[ "$1" == "--fail" ]] ; then 89 can_fail=1 90 shift 91 fi 92 93 local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1) 94 local result=$(sysctl --values kernel.ftrace_enabled) 95 96 if [[ "$result" != "$1" ]] ; then 97 if [[ $can_fail -eq 1 ]] ; then 98 echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg 99 return 100 fi 101 102 skip "failed to set kernel.ftrace_enabled = $1" 103 fi 104 105 echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg 106} 107 108function cleanup() { 109 pop_config 110} 111 112# setup_config - save the current config and set a script exit trap that 113# restores the original config. Setup the dynamic debug 114# for verbose livepatching output and turn on 115# the ftrace_enabled sysctl. 116function setup_config() { 117 is_root 118 has_kdir 119 push_config 120 set_dynamic_debug 121 set_ftrace_enabled 1 122 trap cleanup EXIT INT TERM HUP 123} 124 125# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 126# sleep $RETRY_INTERVAL between attempts 127# cmd - command and its arguments to run 128function loop_until() { 129 local cmd="$*" 130 local i=0 131 while true; do 132 eval "$cmd" && return 0 133 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 134 sleep $RETRY_INTERVAL 135 done 136} 137 138function is_livepatch_mod() { 139 local mod="$1" 140 141 if [[ ! -f "test_modules/$mod.ko" ]]; then 142 die "Can't find \"test_modules/$mod.ko\", try \"make\"" 143 fi 144 145 if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 146 return 0 147 fi 148 149 return 1 150} 151 152function __load_mod() { 153 local mod="$1"; shift 154 155 local msg="% insmod test_modules/$mod.ko $*" 156 log "${msg%% }" 157 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 158 if [[ "$ret" != "" ]]; then 159 die "$ret" 160 fi 161 162 # Wait for module in sysfs ... 163 loop_until '[[ -e "/sys/module/$mod" ]]' || 164 die "failed to load module $mod" 165} 166 167 168# load_mod(modname, params) - load a kernel module 169# modname - module name to load 170# params - module parameters to pass to insmod 171function load_mod() { 172 local mod="$1"; shift 173 174 is_livepatch_mod "$mod" && 175 die "use load_lp() to load the livepatch module $mod" 176 177 __load_mod "$mod" "$@" 178} 179 180# load_lp_nowait(modname, params) - load a kernel module with a livepatch 181# but do not wait on until the transition finishes 182# modname - module name to load 183# params - module parameters to pass to insmod 184function load_lp_nowait() { 185 local mod="$1"; shift 186 187 is_livepatch_mod "$mod" || 188 die "module $mod is not a livepatch" 189 190 __load_mod "$mod" "$@" 191 192 # Wait for livepatch in sysfs ... 193 loop_until '[[ -e "$SYSFS_KLP_DIR/$mod" ]]' || 194 die "failed to load module $mod (sysfs)" 195} 196 197# load_lp(modname, params) - load a kernel module with a livepatch 198# modname - module name to load 199# params - module parameters to pass to insmod 200function load_lp() { 201 local mod="$1"; shift 202 203 load_lp_nowait "$mod" "$@" 204 205 # Wait until the transition finishes ... 206 loop_until 'grep -q '^0$' $SYSFS_KLP_DIR/$mod/transition' || 207 die "failed to complete transition" 208} 209 210# load_failing_mod(modname, params) - load a kernel module, expect to fail 211# modname - module name to load 212# params - module parameters to pass to insmod 213function load_failing_mod() { 214 local mod="$1"; shift 215 216 local msg="% insmod test_modules/$mod.ko $*" 217 log "${msg%% }" 218 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 219 if [[ "$ret" == "" ]]; then 220 die "$mod unexpectedly loaded" 221 fi 222 log "$ret" 223} 224 225# unload_mod(modname) - unload a kernel module 226# modname - module name to unload 227function unload_mod() { 228 local mod="$1" 229 230 # Wait for module reference count to clear ... 231 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 232 die "failed to unload module $mod (refcnt)" 233 234 log "% rmmod $mod" 235 ret=$(rmmod "$mod" 2>&1) 236 if [[ "$ret" != "" ]]; then 237 die "$ret" 238 fi 239 240 # Wait for module in sysfs ... 241 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 242 die "failed to unload module $mod (/sys/module)" 243} 244 245# unload_lp(modname) - unload a kernel module with a livepatch 246# modname - module name to unload 247function unload_lp() { 248 unload_mod "$1" 249} 250 251# disable_lp(modname) - disable a livepatch 252# modname - module name to unload 253function disable_lp() { 254 local mod="$1" 255 256 log "% echo 0 > $SYSFS_KLP_DIR/$mod/enabled" 257 echo 0 > "$SYSFS_KLP_DIR/$mod/enabled" 258 259 # Wait until the transition finishes and the livepatch gets 260 # removed from sysfs... 261 loop_until '[[ ! -e "$SYSFS_KLP_DIR/$mod" ]]' || 262 die "failed to disable livepatch $mod" 263} 264 265# set_pre_patch_ret(modname, pre_patch_ret) 266# modname - module name to set 267# pre_patch_ret - new pre_patch_ret value 268function set_pre_patch_ret { 269 local mod="$1"; shift 270 local ret="$1" 271 272 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 273 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 274 275 # Wait for sysfs value to hold ... 276 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 277 die "failed to set pre_patch_ret parameter for $mod module" 278} 279 280function start_test { 281 local test="$1" 282 283 # Dump something unique into the dmesg log, then stash the entry 284 # in LAST_DMESG. The check_result() function will use it to 285 # find new kernel messages since the test started. 286 local last_dmesg_msg="livepatch kselftest timestamp: $(date --rfc-3339=ns)" 287 log "$last_dmesg_msg" 288 loop_until 'dmesg | grep -q "$last_dmesg_msg"' || 289 die "buffer busy? can't find canary dmesg message: $last_dmesg_msg" 290 LAST_DMESG=$(dmesg | grep "$last_dmesg_msg") 291 292 echo -n "TEST: $test ... " 293 log "===== TEST: $test =====" 294} 295 296# check_result() - verify dmesg output 297# TODO - better filter, out of order msgs, etc? 298function check_result { 299 local expect="$*" 300 local result 301 302 # Test results include any new dmesg entry since LAST_DMESG, then: 303 # - include lines matching keywords 304 # - exclude lines matching keywords 305 # - filter out dmesg timestamp prefixes 306 result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \ 307 grep -e 'livepatch:' -e 'test_klp' | \ 308 grep -v '\(tainting\|taints\) kernel' | \ 309 sed 's/^\[[ 0-9.]*\] //') 310 311 if [[ "$expect" == "$result" ]] ; then 312 echo "ok" 313 elif [[ "$result" == "" ]] ; then 314 echo -e "not ok\n\nbuffer overrun? can't find canary dmesg entry: $LAST_DMESG\n" 315 die "livepatch kselftest(s) failed" 316 else 317 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 318 die "livepatch kselftest(s) failed" 319 fi 320} 321 322# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs 323# path permissions 324# modname - livepatch module creating the sysfs interface 325# rel_path - relative path of the sysfs interface 326# expected_rights - expected access rights 327function check_sysfs_rights() { 328 local mod="$1"; shift 329 local rel_path="$1"; shift 330 local expected_rights="$1"; shift 331 332 local path="$SYSFS_KLP_DIR/$mod/$rel_path" 333 local rights=$(/bin/stat --format '%A' "$path") 334 if test "$rights" != "$expected_rights" ; then 335 die "Unexpected access rights of $path: $expected_rights vs. $rights" 336 fi 337} 338 339# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value 340# modname - livepatch module creating the sysfs interface 341# rel_path - relative path of the sysfs interface 342# expected_value - expected value read from the file 343function check_sysfs_value() { 344 local mod="$1"; shift 345 local rel_path="$1"; shift 346 local expected_value="$1"; shift 347 348 local path="$SYSFS_KLP_DIR/$mod/$rel_path" 349 local value=`cat $path` 350 if test "$value" != "$expected_value" ; then 351 die "Unexpected value in $path: $expected_value vs. $value" 352 fi 353} 354