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 9 10# Kselftest framework requirement - SKIP code is 4 11ksft_skip=4 12 13# log(msg) - write message to kernel log 14# msg - insightful words 15function log() { 16 echo "$1" > /dev/kmsg 17} 18 19# skip(msg) - testing can't proceed 20# msg - explanation 21function skip() { 22 log "SKIP: $1" 23 echo "SKIP: $1" >&2 24 exit $ksft_skip 25} 26 27# root test 28function is_root() { 29 uid=$(id -u) 30 if [ $uid -ne 0 ]; then 31 echo "skip all tests: must be run as root" >&2 32 exit $ksft_skip 33 fi 34} 35 36# die(msg) - game over, man 37# msg - dying words 38function die() { 39 log "ERROR: $1" 40 echo "ERROR: $1" >&2 41 exit 1 42} 43 44# save existing dmesg so we can detect new content 45function save_dmesg() { 46 SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX) 47 dmesg > "$SAVED_DMESG" 48} 49 50# cleanup temporary dmesg file from save_dmesg() 51function cleanup_dmesg_file() { 52 rm -f "$SAVED_DMESG" 53} 54 55function push_config() { 56 DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \ 57 awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}') 58 FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled) 59} 60 61function pop_config() { 62 if [[ -n "$DYNAMIC_DEBUG" ]]; then 63 echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control 64 fi 65 if [[ -n "$FTRACE_ENABLED" ]]; then 66 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null 67 fi 68} 69 70function set_dynamic_debug() { 71 cat <<-EOF > /sys/kernel/debug/dynamic_debug/control 72 file kernel/livepatch/* +p 73 func klp_try_switch_task -p 74 EOF 75} 76 77function set_ftrace_enabled() { 78 local can_fail=0 79 if [[ "$1" == "--fail" ]] ; then 80 can_fail=1 81 shift 82 fi 83 84 local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1) 85 local result=$(sysctl --values kernel.ftrace_enabled) 86 87 if [[ "$result" != "$1" ]] ; then 88 if [[ $can_fail -eq 1 ]] ; then 89 echo "livepatch: $err" > /dev/kmsg 90 return 91 fi 92 93 skip "failed to set kernel.ftrace_enabled = $1" 94 fi 95 96 echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg 97} 98 99function cleanup() { 100 pop_config 101 cleanup_dmesg_file 102} 103 104# setup_config - save the current config and set a script exit trap that 105# restores the original config. Setup the dynamic debug 106# for verbose livepatching output and turn on 107# the ftrace_enabled sysctl. 108function setup_config() { 109 is_root 110 push_config 111 set_dynamic_debug 112 set_ftrace_enabled 1 113 trap cleanup EXIT INT TERM HUP 114} 115 116# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 117# sleep $RETRY_INTERVAL between attempts 118# cmd - command and its arguments to run 119function loop_until() { 120 local cmd="$*" 121 local i=0 122 while true; do 123 eval "$cmd" && return 0 124 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 125 sleep $RETRY_INTERVAL 126 done 127} 128 129function assert_mod() { 130 local mod="$1" 131 132 modprobe --dry-run "$mod" &>/dev/null 133} 134 135function is_livepatch_mod() { 136 local mod="$1" 137 138 if [[ $(modinfo "$mod" | 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="% modprobe $mod $*" 149 log "${msg%% }" 150 ret=$(modprobe "$mod" "$@" 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 modprobe 164function load_mod() { 165 local mod="$1"; shift 166 167 assert_mod "$mod" || 168 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 169 170 is_livepatch_mod "$mod" && 171 die "use load_lp() to load the livepatch module $mod" 172 173 __load_mod "$mod" "$@" 174} 175 176# load_lp_nowait(modname, params) - load a kernel module with a livepatch 177# but do not wait on until the transition finishes 178# modname - module name to load 179# params - module parameters to pass to modprobe 180function load_lp_nowait() { 181 local mod="$1"; shift 182 183 assert_mod "$mod" || 184 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 185 186 is_livepatch_mod "$mod" || 187 die "module $mod is not a livepatch" 188 189 __load_mod "$mod" "$@" 190 191 # Wait for livepatch in sysfs ... 192 loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' || 193 die "failed to load module $mod (sysfs)" 194} 195 196# load_lp(modname, params) - load a kernel module with a livepatch 197# modname - module name to load 198# params - module parameters to pass to modprobe 199function load_lp() { 200 local mod="$1"; shift 201 202 load_lp_nowait "$mod" "$@" 203 204 # Wait until the transition finishes ... 205 loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' || 206 die "failed to complete transition" 207} 208 209# load_failing_mod(modname, params) - load a kernel module, expect to fail 210# modname - module name to load 211# params - module parameters to pass to modprobe 212function load_failing_mod() { 213 local mod="$1"; shift 214 215 local msg="% modprobe $mod $*" 216 log "${msg%% }" 217 ret=$(modprobe "$mod" "$@" 2>&1) 218 if [[ "$ret" == "" ]]; then 219 die "$mod unexpectedly loaded" 220 fi 221 log "$ret" 222} 223 224# unload_mod(modname) - unload a kernel module 225# modname - module name to unload 226function unload_mod() { 227 local mod="$1" 228 229 # Wait for module reference count to clear ... 230 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 231 die "failed to unload module $mod (refcnt)" 232 233 log "% rmmod $mod" 234 ret=$(rmmod "$mod" 2>&1) 235 if [[ "$ret" != "" ]]; then 236 die "$ret" 237 fi 238 239 # Wait for module in sysfs ... 240 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 241 die "failed to unload module $mod (/sys/module)" 242} 243 244# unload_lp(modname) - unload a kernel module with a livepatch 245# modname - module name to unload 246function unload_lp() { 247 unload_mod "$1" 248} 249 250# disable_lp(modname) - disable a livepatch 251# modname - module name to unload 252function disable_lp() { 253 local mod="$1" 254 255 log "% echo 0 > /sys/kernel/livepatch/$mod/enabled" 256 echo 0 > /sys/kernel/livepatch/"$mod"/enabled 257 258 # Wait until the transition finishes and the livepatch gets 259 # removed from sysfs... 260 loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' || 261 die "failed to disable livepatch $mod" 262} 263 264# set_pre_patch_ret(modname, pre_patch_ret) 265# modname - module name to set 266# pre_patch_ret - new pre_patch_ret value 267function set_pre_patch_ret { 268 local mod="$1"; shift 269 local ret="$1" 270 271 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 272 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 273 274 # Wait for sysfs value to hold ... 275 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 276 die "failed to set pre_patch_ret parameter for $mod module" 277} 278 279function start_test { 280 local test="$1" 281 282 save_dmesg 283 echo -n "TEST: $test ... " 284 log "===== TEST: $test =====" 285} 286 287# check_result() - verify dmesg output 288# TODO - better filter, out of order msgs, etc? 289function check_result { 290 local expect="$*" 291 local result 292 293 # Note: when comparing dmesg output, the kernel log timestamps 294 # help differentiate repeated testing runs. Remove them with a 295 # post-comparison sed filter. 296 297 result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \ 298 grep -e 'livepatch:' -e 'test_klp' | \ 299 grep -v '\(tainting\|taints\) kernel' | \ 300 sed 's/^\[[ 0-9.]*\] //') 301 302 if [[ "$expect" == "$result" ]] ; then 303 echo "ok" 304 else 305 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 306 die "livepatch kselftest(s) failed" 307 fi 308 309 cleanup_dmesg_file 310} 311