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 57# save existing dmesg so we can detect new content 58function save_dmesg() { 59 SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX) 60 dmesg > "$SAVED_DMESG" 61} 62 63# cleanup temporary dmesg file from save_dmesg() 64function cleanup_dmesg_file() { 65 rm -f "$SAVED_DMESG" 66} 67 68function push_config() { 69 DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \ 70 awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}') 71 FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled) 72} 73 74function pop_config() { 75 if [[ -n "$DYNAMIC_DEBUG" ]]; then 76 echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control 77 fi 78 if [[ -n "$FTRACE_ENABLED" ]]; then 79 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null 80 fi 81} 82 83function set_dynamic_debug() { 84 cat <<-EOF > /sys/kernel/debug/dynamic_debug/control 85 file kernel/livepatch/* +p 86 func klp_try_switch_task -p 87 EOF 88} 89 90function set_ftrace_enabled() { 91 local can_fail=0 92 if [[ "$1" == "--fail" ]] ; then 93 can_fail=1 94 shift 95 fi 96 97 local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1) 98 local result=$(sysctl --values kernel.ftrace_enabled) 99 100 if [[ "$result" != "$1" ]] ; then 101 if [[ $can_fail -eq 1 ]] ; then 102 echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg 103 return 104 fi 105 106 skip "failed to set kernel.ftrace_enabled = $1" 107 fi 108 109 echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg 110} 111 112function cleanup() { 113 pop_config 114 cleanup_dmesg_file 115} 116 117# setup_config - save the current config and set a script exit trap that 118# restores the original config. Setup the dynamic debug 119# for verbose livepatching output and turn on 120# the ftrace_enabled sysctl. 121function setup_config() { 122 is_root 123 has_kdir 124 push_config 125 set_dynamic_debug 126 set_ftrace_enabled 1 127 trap cleanup EXIT INT TERM HUP 128} 129 130# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 131# sleep $RETRY_INTERVAL between attempts 132# cmd - command and its arguments to run 133function loop_until() { 134 local cmd="$*" 135 local i=0 136 while true; do 137 eval "$cmd" && return 0 138 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 139 sleep $RETRY_INTERVAL 140 done 141} 142 143function is_livepatch_mod() { 144 local mod="$1" 145 146 if [[ ! -f "test_modules/$mod.ko" ]]; then 147 die "Can't find \"test_modules/$mod.ko\", try \"make\"" 148 fi 149 150 if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 151 return 0 152 fi 153 154 return 1 155} 156 157function __load_mod() { 158 local mod="$1"; shift 159 160 local msg="% insmod test_modules/$mod.ko $*" 161 log "${msg%% }" 162 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 163 if [[ "$ret" != "" ]]; then 164 die "$ret" 165 fi 166 167 # Wait for module in sysfs ... 168 loop_until '[[ -e "/sys/module/$mod" ]]' || 169 die "failed to load module $mod" 170} 171 172 173# load_mod(modname, params) - load a kernel module 174# modname - module name to load 175# params - module parameters to pass to insmod 176function load_mod() { 177 local mod="$1"; shift 178 179 is_livepatch_mod "$mod" && 180 die "use load_lp() to load the livepatch module $mod" 181 182 __load_mod "$mod" "$@" 183} 184 185# load_lp_nowait(modname, params) - load a kernel module with a livepatch 186# but do not wait on until the transition finishes 187# modname - module name to load 188# params - module parameters to pass to insmod 189function load_lp_nowait() { 190 local mod="$1"; shift 191 192 is_livepatch_mod "$mod" || 193 die "module $mod is not a livepatch" 194 195 __load_mod "$mod" "$@" 196 197 # Wait for livepatch in sysfs ... 198 loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' || 199 die "failed to load module $mod (sysfs)" 200} 201 202# load_lp(modname, params) - load a kernel module with a livepatch 203# modname - module name to load 204# params - module parameters to pass to insmod 205function load_lp() { 206 local mod="$1"; shift 207 208 load_lp_nowait "$mod" "$@" 209 210 # Wait until the transition finishes ... 211 loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' || 212 die "failed to complete transition" 213} 214 215# load_failing_mod(modname, params) - load a kernel module, expect to fail 216# modname - module name to load 217# params - module parameters to pass to insmod 218function load_failing_mod() { 219 local mod="$1"; shift 220 221 local msg="% insmod test_modules/$mod.ko $*" 222 log "${msg%% }" 223 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 224 if [[ "$ret" == "" ]]; then 225 die "$mod unexpectedly loaded" 226 fi 227 log "$ret" 228} 229 230# unload_mod(modname) - unload a kernel module 231# modname - module name to unload 232function unload_mod() { 233 local mod="$1" 234 235 # Wait for module reference count to clear ... 236 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 237 die "failed to unload module $mod (refcnt)" 238 239 log "% rmmod $mod" 240 ret=$(rmmod "$mod" 2>&1) 241 if [[ "$ret" != "" ]]; then 242 die "$ret" 243 fi 244 245 # Wait for module in sysfs ... 246 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 247 die "failed to unload module $mod (/sys/module)" 248} 249 250# unload_lp(modname) - unload a kernel module with a livepatch 251# modname - module name to unload 252function unload_lp() { 253 unload_mod "$1" 254} 255 256# disable_lp(modname) - disable a livepatch 257# modname - module name to unload 258function disable_lp() { 259 local mod="$1" 260 261 log "% echo 0 > /sys/kernel/livepatch/$mod/enabled" 262 echo 0 > /sys/kernel/livepatch/"$mod"/enabled 263 264 # Wait until the transition finishes and the livepatch gets 265 # removed from sysfs... 266 loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' || 267 die "failed to disable livepatch $mod" 268} 269 270# set_pre_patch_ret(modname, pre_patch_ret) 271# modname - module name to set 272# pre_patch_ret - new pre_patch_ret value 273function set_pre_patch_ret { 274 local mod="$1"; shift 275 local ret="$1" 276 277 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 278 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 279 280 # Wait for sysfs value to hold ... 281 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 282 die "failed to set pre_patch_ret parameter for $mod module" 283} 284 285function start_test { 286 local test="$1" 287 288 save_dmesg 289 echo -n "TEST: $test ... " 290 log "===== TEST: $test =====" 291} 292 293# check_result() - verify dmesg output 294# TODO - better filter, out of order msgs, etc? 295function check_result { 296 local expect="$*" 297 local result 298 299 # Note: when comparing dmesg output, the kernel log timestamps 300 # help differentiate repeated testing runs. Remove them with a 301 # post-comparison sed filter. 302 303 result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \ 304 grep -e 'livepatch:' -e 'test_klp' | \ 305 grep -v '\(tainting\|taints\) kernel' | \ 306 sed 's/^\[[ 0-9.]*\] //') 307 308 if [[ "$expect" == "$result" ]] ; then 309 echo "ok" 310 else 311 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 312 die "livepatch kselftest(s) failed" 313 fi 314 315 cleanup_dmesg_file 316} 317 318# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs 319# path permissions 320# modname - livepatch module creating the sysfs interface 321# rel_path - relative path of the sysfs interface 322# expected_rights - expected access rights 323function check_sysfs_rights() { 324 local mod="$1"; shift 325 local rel_path="$1"; shift 326 local expected_rights="$1"; shift 327 328 local path="$KLP_SYSFS_DIR/$mod/$rel_path" 329 local rights=$(/bin/stat --format '%A' "$path") 330 if test "$rights" != "$expected_rights" ; then 331 die "Unexpected access rights of $path: $expected_rights vs. $rights" 332 fi 333} 334 335# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value 336# modname - livepatch module creating the sysfs interface 337# rel_path - relative path of the sysfs interface 338# expected_value - expected value read from the file 339function check_sysfs_value() { 340 local mod="$1"; shift 341 local rel_path="$1"; shift 342 local expected_value="$1"; shift 343 344 local path="$KLP_SYSFS_DIR/$mod/$rel_path" 345 local value=`cat $path` 346 if test "$value" != "$expected_value" ; then 347 die "Unexpected value in $path: $expected_value vs. $value" 348 fi 349} 350