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 assert_mod() { 131 local mod="$1" 132 133 modprobe --dry-run "$mod" &>/dev/null 134} 135 136function is_livepatch_mod() { 137 local mod="$1" 138 139 if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 140 return 0 141 fi 142 143 return 1 144} 145 146function __load_mod() { 147 local mod="$1"; shift 148 149 local msg="% modprobe $mod $*" 150 log "${msg%% }" 151 ret=$(modprobe "$mod" "$@" 2>&1) 152 if [[ "$ret" != "" ]]; then 153 die "$ret" 154 fi 155 156 # Wait for module in sysfs ... 157 loop_until '[[ -e "/sys/module/$mod" ]]' || 158 die "failed to load module $mod" 159} 160 161 162# load_mod(modname, params) - load a kernel module 163# modname - module name to load 164# params - module parameters to pass to modprobe 165function load_mod() { 166 local mod="$1"; shift 167 168 assert_mod "$mod" || 169 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 170 171 is_livepatch_mod "$mod" && 172 die "use load_lp() to load the livepatch module $mod" 173 174 __load_mod "$mod" "$@" 175} 176 177# load_lp_nowait(modname, params) - load a kernel module with a livepatch 178# but do not wait on until the transition finishes 179# modname - module name to load 180# params - module parameters to pass to modprobe 181function load_lp_nowait() { 182 local mod="$1"; shift 183 184 assert_mod "$mod" || 185 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 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 "/sys/kernel/livepatch/$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 modprobe 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$' /sys/kernel/livepatch/$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 modprobe 213function load_failing_mod() { 214 local mod="$1"; shift 215 216 local msg="% modprobe $mod $*" 217 log "${msg%% }" 218 ret=$(modprobe "$mod" "$@" 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 > /sys/kernel/livepatch/$mod/enabled" 257 echo 0 > /sys/kernel/livepatch/"$mod"/enabled 258 259 # Wait until the transition finishes and the livepatch gets 260 # removed from sysfs... 261 loop_until '[[ ! -e "/sys/kernel/livepatch/$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 save_dmesg 284 echo -n "TEST: $test ... " 285 log "===== TEST: $test =====" 286} 287 288# check_result() - verify dmesg output 289# TODO - better filter, out of order msgs, etc? 290function check_result { 291 local expect="$*" 292 local result 293 294 # Note: when comparing dmesg output, the kernel log timestamps 295 # help differentiate repeated testing runs. Remove them with a 296 # post-comparison sed filter. 297 298 result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \ 299 grep -e 'livepatch:' -e 'test_klp' | \ 300 grep -v '\(tainting\|taints\) kernel' | \ 301 sed 's/^\[[ 0-9.]*\] //') 302 303 if [[ "$expect" == "$result" ]] ; then 304 echo "ok" 305 else 306 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 307 die "livepatch kselftest(s) failed" 308 fi 309 310 cleanup_dmesg_file 311} 312 313# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs 314# path permissions 315# modname - livepatch module creating the sysfs interface 316# rel_path - relative path of the sysfs interface 317# expected_rights - expected access rights 318function check_sysfs_rights() { 319 local mod="$1"; shift 320 local rel_path="$1"; shift 321 local expected_rights="$1"; shift 322 323 local path="$KLP_SYSFS_DIR/$mod/$rel_path" 324 local rights=$(/bin/stat --format '%A' "$path") 325 if test "$rights" != "$expected_rights" ; then 326 die "Unexpected access rights of $path: $expected_rights vs. $rights" 327 fi 328} 329 330# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value 331# modname - livepatch module creating the sysfs interface 332# rel_path - relative path of the sysfs interface 333# expected_value - expected value read from the file 334function check_sysfs_value() { 335 local mod="$1"; shift 336 local rel_path="$1"; shift 337 local expected_value="$1"; shift 338 339 local path="$KLP_SYSFS_DIR/$mod/$rel_path" 340 local value=`cat $path` 341 if test "$value" != "$expected_value" ; then 342 die "Unexpected value in $path: $expected_value vs. $value" 343 fi 344} 345