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 result=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1 && \ 79 sysctl kernel.ftrace_enabled 2>&1) 80 echo "livepatch: $result" > /dev/kmsg 81} 82 83function cleanup() { 84 pop_config 85 cleanup_dmesg_file 86} 87 88# setup_config - save the current config and set a script exit trap that 89# restores the original config. Setup the dynamic debug 90# for verbose livepatching output and turn on 91# the ftrace_enabled sysctl. 92function setup_config() { 93 is_root 94 push_config 95 set_dynamic_debug 96 set_ftrace_enabled 1 97 trap cleanup EXIT INT TERM HUP 98} 99 100# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 101# sleep $RETRY_INTERVAL between attempts 102# cmd - command and its arguments to run 103function loop_until() { 104 local cmd="$*" 105 local i=0 106 while true; do 107 eval "$cmd" && return 0 108 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 109 sleep $RETRY_INTERVAL 110 done 111} 112 113function assert_mod() { 114 local mod="$1" 115 116 modprobe --dry-run "$mod" &>/dev/null 117} 118 119function is_livepatch_mod() { 120 local mod="$1" 121 122 if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 123 return 0 124 fi 125 126 return 1 127} 128 129function __load_mod() { 130 local mod="$1"; shift 131 132 local msg="% modprobe $mod $*" 133 log "${msg%% }" 134 ret=$(modprobe "$mod" "$@" 2>&1) 135 if [[ "$ret" != "" ]]; then 136 die "$ret" 137 fi 138 139 # Wait for module in sysfs ... 140 loop_until '[[ -e "/sys/module/$mod" ]]' || 141 die "failed to load module $mod" 142} 143 144 145# load_mod(modname, params) - load a kernel module 146# modname - module name to load 147# params - module parameters to pass to modprobe 148function load_mod() { 149 local mod="$1"; shift 150 151 assert_mod "$mod" || 152 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 153 154 is_livepatch_mod "$mod" && 155 die "use load_lp() to load the livepatch module $mod" 156 157 __load_mod "$mod" "$@" 158} 159 160# load_lp_nowait(modname, params) - load a kernel module with a livepatch 161# but do not wait on until the transition finishes 162# modname - module name to load 163# params - module parameters to pass to modprobe 164function load_lp_nowait() { 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 "module $mod is not a livepatch" 172 173 __load_mod "$mod" "$@" 174 175 # Wait for livepatch in sysfs ... 176 loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' || 177 die "failed to load module $mod (sysfs)" 178} 179 180# load_lp(modname, params) - load a kernel module with a livepatch 181# modname - module name to load 182# params - module parameters to pass to modprobe 183function load_lp() { 184 local mod="$1"; shift 185 186 load_lp_nowait "$mod" "$@" 187 188 # Wait until the transition finishes ... 189 loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' || 190 die "failed to complete transition" 191} 192 193# load_failing_mod(modname, params) - load a kernel module, expect to fail 194# modname - module name to load 195# params - module parameters to pass to modprobe 196function load_failing_mod() { 197 local mod="$1"; shift 198 199 local msg="% modprobe $mod $*" 200 log "${msg%% }" 201 ret=$(modprobe "$mod" "$@" 2>&1) 202 if [[ "$ret" == "" ]]; then 203 die "$mod unexpectedly loaded" 204 fi 205 log "$ret" 206} 207 208# unload_mod(modname) - unload a kernel module 209# modname - module name to unload 210function unload_mod() { 211 local mod="$1" 212 213 # Wait for module reference count to clear ... 214 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 215 die "failed to unload module $mod (refcnt)" 216 217 log "% rmmod $mod" 218 ret=$(rmmod "$mod" 2>&1) 219 if [[ "$ret" != "" ]]; then 220 die "$ret" 221 fi 222 223 # Wait for module in sysfs ... 224 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 225 die "failed to unload module $mod (/sys/module)" 226} 227 228# unload_lp(modname) - unload a kernel module with a livepatch 229# modname - module name to unload 230function unload_lp() { 231 unload_mod "$1" 232} 233 234# disable_lp(modname) - disable a livepatch 235# modname - module name to unload 236function disable_lp() { 237 local mod="$1" 238 239 log "% echo 0 > /sys/kernel/livepatch/$mod/enabled" 240 echo 0 > /sys/kernel/livepatch/"$mod"/enabled 241 242 # Wait until the transition finishes and the livepatch gets 243 # removed from sysfs... 244 loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' || 245 die "failed to disable livepatch $mod" 246} 247 248# set_pre_patch_ret(modname, pre_patch_ret) 249# modname - module name to set 250# pre_patch_ret - new pre_patch_ret value 251function set_pre_patch_ret { 252 local mod="$1"; shift 253 local ret="$1" 254 255 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 256 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 257 258 # Wait for sysfs value to hold ... 259 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 260 die "failed to set pre_patch_ret parameter for $mod module" 261} 262 263function start_test { 264 local test="$1" 265 266 save_dmesg 267 echo -n "TEST: $test ... " 268 log "===== TEST: $test =====" 269} 270 271# check_result() - verify dmesg output 272# TODO - better filter, out of order msgs, etc? 273function check_result { 274 local expect="$*" 275 local result 276 277 # Note: when comparing dmesg output, the kernel log timestamps 278 # help differentiate repeated testing runs. Remove them with a 279 # post-comparison sed filter. 280 281 result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \ 282 grep -e 'livepatch:' -e 'test_klp' | \ 283 grep -v '\(tainting\|taints\) kernel' | \ 284 sed 's/^\[[ 0-9.]*\] //') 285 286 if [[ "$expect" == "$result" ]] ; then 287 echo "ok" 288 else 289 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 290 die "livepatch kselftest(s) failed" 291 fi 292 293 cleanup_dmesg_file 294} 295