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 44function push_config() { 45 DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \ 46 awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}') 47 FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled) 48} 49 50function pop_config() { 51 if [[ -n "$DYNAMIC_DEBUG" ]]; then 52 echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control 53 fi 54 if [[ -n "$FTRACE_ENABLED" ]]; then 55 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null 56 fi 57} 58 59function set_dynamic_debug() { 60 cat <<-EOF > /sys/kernel/debug/dynamic_debug/control 61 file kernel/livepatch/* +p 62 func klp_try_switch_task -p 63 EOF 64} 65 66function set_ftrace_enabled() { 67 local sysctl="$1" 68 result=$(sysctl kernel.ftrace_enabled="$1" 2>&1 | paste --serial --delimiters=' ') 69 echo "livepatch: $result" > /dev/kmsg 70} 71 72# setup_config - save the current config and set a script exit trap that 73# restores the original config. Setup the dynamic debug 74# for verbose livepatching output and turn on 75# the ftrace_enabled sysctl. 76function setup_config() { 77 is_root 78 push_config 79 set_dynamic_debug 80 set_ftrace_enabled 1 81 trap pop_config EXIT INT TERM HUP 82} 83 84# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 85# sleep $RETRY_INTERVAL between attempts 86# cmd - command and its arguments to run 87function loop_until() { 88 local cmd="$*" 89 local i=0 90 while true; do 91 eval "$cmd" && return 0 92 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 93 sleep $RETRY_INTERVAL 94 done 95} 96 97function assert_mod() { 98 local mod="$1" 99 100 modprobe --dry-run "$mod" &>/dev/null 101} 102 103function is_livepatch_mod() { 104 local mod="$1" 105 106 if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 107 return 0 108 fi 109 110 return 1 111} 112 113function __load_mod() { 114 local mod="$1"; shift 115 116 local msg="% modprobe $mod $*" 117 log "${msg%% }" 118 ret=$(modprobe "$mod" "$@" 2>&1) 119 if [[ "$ret" != "" ]]; then 120 die "$ret" 121 fi 122 123 # Wait for module in sysfs ... 124 loop_until '[[ -e "/sys/module/$mod" ]]' || 125 die "failed to load module $mod" 126} 127 128 129# load_mod(modname, params) - load a kernel module 130# modname - module name to load 131# params - module parameters to pass to modprobe 132function load_mod() { 133 local mod="$1"; shift 134 135 assert_mod "$mod" || 136 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root" 137 138 is_livepatch_mod "$mod" && 139 die "use load_lp() to load the livepatch module $mod" 140 141 __load_mod "$mod" "$@" 142} 143 144# load_lp_nowait(modname, params) - load a kernel module with a livepatch 145# but do not wait on until the transition finishes 146# modname - module name to load 147# params - module parameters to pass to modprobe 148function load_lp_nowait() { 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 "module $mod is not a livepatch" 156 157 __load_mod "$mod" "$@" 158 159 # Wait for livepatch in sysfs ... 160 loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' || 161 die "failed to load module $mod (sysfs)" 162} 163 164# load_lp(modname, params) - load a kernel module with a livepatch 165# modname - module name to load 166# params - module parameters to pass to modprobe 167function load_lp() { 168 local mod="$1"; shift 169 170 load_lp_nowait "$mod" "$@" 171 172 # Wait until the transition finishes ... 173 loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' || 174 die "failed to complete transition" 175} 176 177# load_failing_mod(modname, params) - load a kernel module, expect to fail 178# modname - module name to load 179# params - module parameters to pass to modprobe 180function load_failing_mod() { 181 local mod="$1"; shift 182 183 local msg="% modprobe $mod $*" 184 log "${msg%% }" 185 ret=$(modprobe "$mod" "$@" 2>&1) 186 if [[ "$ret" == "" ]]; then 187 die "$mod unexpectedly loaded" 188 fi 189 log "$ret" 190} 191 192# unload_mod(modname) - unload a kernel module 193# modname - module name to unload 194function unload_mod() { 195 local mod="$1" 196 197 # Wait for module reference count to clear ... 198 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 199 die "failed to unload module $mod (refcnt)" 200 201 log "% rmmod $mod" 202 ret=$(rmmod "$mod" 2>&1) 203 if [[ "$ret" != "" ]]; then 204 die "$ret" 205 fi 206 207 # Wait for module in sysfs ... 208 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 209 die "failed to unload module $mod (/sys/module)" 210} 211 212# unload_lp(modname) - unload a kernel module with a livepatch 213# modname - module name to unload 214function unload_lp() { 215 unload_mod "$1" 216} 217 218# disable_lp(modname) - disable a livepatch 219# modname - module name to unload 220function disable_lp() { 221 local mod="$1" 222 223 log "% echo 0 > /sys/kernel/livepatch/$mod/enabled" 224 echo 0 > /sys/kernel/livepatch/"$mod"/enabled 225 226 # Wait until the transition finishes and the livepatch gets 227 # removed from sysfs... 228 loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' || 229 die "failed to disable livepatch $mod" 230} 231 232# set_pre_patch_ret(modname, pre_patch_ret) 233# modname - module name to set 234# pre_patch_ret - new pre_patch_ret value 235function set_pre_patch_ret { 236 local mod="$1"; shift 237 local ret="$1" 238 239 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 240 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 241 242 # Wait for sysfs value to hold ... 243 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 244 die "failed to set pre_patch_ret parameter for $mod module" 245} 246 247# check_result() - verify dmesg output 248# TODO - better filter, out of order msgs, etc? 249function check_result { 250 local expect="$*" 251 local result 252 253 result=$(dmesg | grep -v 'tainting' | grep -e 'livepatch:' -e 'test_klp' | sed 's/^\[[ 0-9.]*\] //') 254 255 if [[ "$expect" == "$result" ]] ; then 256 echo "ok" 257 else 258 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 259 die "livepatch kselftest(s) failed" 260 fi 261} 262