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