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