347 lines · bash
1#!/bin/bash2# SPDX-License-Identifier: GPL-2.03# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>4 5# Shell functions for the rest of the scripts.6 7MAX_RETRIES=6008RETRY_INTERVAL=".1" # seconds9KLP_SYSFS_DIR="/sys/kernel/livepatch"10 11# Kselftest framework requirement - SKIP code is 412ksft_skip=413 14# log(msg) - write message to kernel log15# msg - insightful words16function log() {17 echo "$1" > /dev/kmsg18}19 20# skip(msg) - testing can't proceed21# msg - explanation22function skip() {23 log "SKIP: $1"24 echo "SKIP: $1" >&225 exit $ksft_skip26}27 28# root test29function is_root() {30 uid=$(id -u)31 if [ $uid -ne 0 ]; then32 echo "skip all tests: must be run as root" >&233 exit $ksft_skip34 fi35}36 37# Check if we can compile the modules before loading them38function has_kdir() {39 if [ -z "$KDIR" ]; then40 KDIR="/lib/modules/$(uname -r)/build"41 fi42 43 if [ ! -d "$KDIR" ]; then44 echo "skip all tests: KDIR ($KDIR) not available to compile modules."45 exit $ksft_skip46 fi47}48 49# die(msg) - game over, man50# msg - dying words51function die() {52 log "ERROR: $1"53 echo "ERROR: $1" >&254 exit 155}56 57function push_config() {58 DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \59 awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')60 FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)61}62 63function pop_config() {64 if [[ -n "$DYNAMIC_DEBUG" ]]; then65 echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control66 fi67 if [[ -n "$FTRACE_ENABLED" ]]; then68 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null69 fi70}71 72function set_dynamic_debug() {73 cat <<-EOF > /sys/kernel/debug/dynamic_debug/control74 file kernel/livepatch/* +p75 func klp_try_switch_task -p76 EOF77}78 79function set_ftrace_enabled() {80 local can_fail=081 if [[ "$1" == "--fail" ]] ; then82 can_fail=183 shift84 fi85 86 local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)87 local result=$(sysctl --values kernel.ftrace_enabled)88 89 if [[ "$result" != "$1" ]] ; then90 if [[ $can_fail -eq 1 ]] ; then91 echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg92 return93 fi94 95 skip "failed to set kernel.ftrace_enabled = $1"96 fi97 98 echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg99}100 101function cleanup() {102 pop_config103}104 105# setup_config - save the current config and set a script exit trap that106# restores the original config. Setup the dynamic debug107# for verbose livepatching output and turn on108# the ftrace_enabled sysctl.109function setup_config() {110 is_root111 has_kdir112 push_config113 set_dynamic_debug114 set_ftrace_enabled 1115 trap cleanup EXIT INT TERM HUP116}117 118# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,119# sleep $RETRY_INTERVAL between attempts120# cmd - command and its arguments to run121function loop_until() {122 local cmd="$*"123 local i=0124 while true; do125 eval "$cmd" && return 0126 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1127 sleep $RETRY_INTERVAL128 done129}130 131function is_livepatch_mod() {132 local mod="$1"133 134 if [[ ! -f "test_modules/$mod.ko" ]]; then135 die "Can't find \"test_modules/$mod.ko\", try \"make\""136 fi137 138 if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then139 return 0140 fi141 142 return 1143}144 145function __load_mod() {146 local mod="$1"; shift147 148 local msg="% insmod test_modules/$mod.ko $*"149 log "${msg%% }"150 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)151 if [[ "$ret" != "" ]]; then152 die "$ret"153 fi154 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 module162# modname - module name to load163# params - module parameters to pass to insmod164function load_mod() {165 local mod="$1"; shift166 167 is_livepatch_mod "$mod" &&168 die "use load_lp() to load the livepatch module $mod"169 170 __load_mod "$mod" "$@"171}172 173# load_lp_nowait(modname, params) - load a kernel module with a livepatch174# but do not wait on until the transition finishes175# modname - module name to load176# params - module parameters to pass to insmod177function load_lp_nowait() {178 local mod="$1"; shift179 180 is_livepatch_mod "$mod" ||181 die "module $mod is not a livepatch"182 183 __load_mod "$mod" "$@"184 185 # Wait for livepatch in sysfs ...186 loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||187 die "failed to load module $mod (sysfs)"188}189 190# load_lp(modname, params) - load a kernel module with a livepatch191# modname - module name to load192# params - module parameters to pass to insmod193function load_lp() {194 local mod="$1"; shift195 196 load_lp_nowait "$mod" "$@"197 198 # Wait until the transition finishes ...199 loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||200 die "failed to complete transition"201}202 203# load_failing_mod(modname, params) - load a kernel module, expect to fail204# modname - module name to load205# params - module parameters to pass to insmod206function load_failing_mod() {207 local mod="$1"; shift208 209 local msg="% insmod test_modules/$mod.ko $*"210 log "${msg%% }"211 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1)212 if [[ "$ret" == "" ]]; then213 die "$mod unexpectedly loaded"214 fi215 log "$ret"216}217 218# unload_mod(modname) - unload a kernel module219# modname - module name to unload220function unload_mod() {221 local mod="$1"222 223 # Wait for module reference count to clear ...224 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||225 die "failed to unload module $mod (refcnt)"226 227 log "% rmmod $mod"228 ret=$(rmmod "$mod" 2>&1)229 if [[ "$ret" != "" ]]; then230 die "$ret"231 fi232 233 # Wait for module in sysfs ...234 loop_until '[[ ! -e "/sys/module/$mod" ]]' ||235 die "failed to unload module $mod (/sys/module)"236}237 238# unload_lp(modname) - unload a kernel module with a livepatch239# modname - module name to unload240function unload_lp() {241 unload_mod "$1"242}243 244# disable_lp(modname) - disable a livepatch245# modname - module name to unload246function disable_lp() {247 local mod="$1"248 249 log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"250 echo 0 > /sys/kernel/livepatch/"$mod"/enabled251 252 # Wait until the transition finishes and the livepatch gets253 # removed from sysfs...254 loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||255 die "failed to disable livepatch $mod"256}257 258# set_pre_patch_ret(modname, pre_patch_ret)259# modname - module name to set260# pre_patch_ret - new pre_patch_ret value261function set_pre_patch_ret {262 local mod="$1"; shift263 local ret="$1"264 265 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"266 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret267 268 # Wait for sysfs value to hold ...269 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||270 die "failed to set pre_patch_ret parameter for $mod module"271}272 273function start_test {274 local test="$1"275 276 # Dump something unique into the dmesg log, then stash the entry277 # in LAST_DMESG. The check_result() function will use it to278 # find new kernel messages since the test started.279 local last_dmesg_msg="livepatch kselftest timestamp: $(date --rfc-3339=ns)"280 log "$last_dmesg_msg"281 loop_until 'dmesg | grep -q "$last_dmesg_msg"' ||282 die "buffer busy? can't find canary dmesg message: $last_dmesg_msg"283 LAST_DMESG=$(dmesg | grep "$last_dmesg_msg")284 285 echo -n "TEST: $test ... "286 log "===== TEST: $test ====="287}288 289# check_result() - verify dmesg output290# TODO - better filter, out of order msgs, etc?291function check_result {292 local expect="$*"293 local result294 295 # Test results include any new dmesg entry since LAST_DMESG, then:296 # - include lines matching keywords297 # - exclude lines matching keywords298 # - filter out dmesg timestamp prefixes299 result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \300 grep -e 'livepatch:' -e 'test_klp' | \301 grep -v '\(tainting\|taints\) kernel' | \302 sed 's/^\[[ 0-9.]*\] //')303 304 if [[ "$expect" == "$result" ]] ; then305 echo "ok"306 elif [[ "$result" == "" ]] ; then307 echo -e "not ok\n\nbuffer overrun? can't find canary dmesg entry: $LAST_DMESG\n"308 die "livepatch kselftest(s) failed"309 else310 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"311 die "livepatch kselftest(s) failed"312 fi313}314 315# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs316# path permissions317# modname - livepatch module creating the sysfs interface318# rel_path - relative path of the sysfs interface319# expected_rights - expected access rights320function check_sysfs_rights() {321 local mod="$1"; shift322 local rel_path="$1"; shift323 local expected_rights="$1"; shift324 325 local path="$KLP_SYSFS_DIR/$mod/$rel_path"326 local rights=$(/bin/stat --format '%A' "$path")327 if test "$rights" != "$expected_rights" ; then328 die "Unexpected access rights of $path: $expected_rights vs. $rights"329 fi330}331 332# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value333# modname - livepatch module creating the sysfs interface334# rel_path - relative path of the sysfs interface335# expected_value - expected value read from the file336function check_sysfs_value() {337 local mod="$1"; shift338 local rel_path="$1"; shift339 local expected_value="$1"; shift340 341 local path="$KLP_SYSFS_DIR/$mod/$rel_path"342 local value=`cat $path`343 if test "$value" != "$expected_value" ; then344 die "Unexpected value in $path: $expected_value vs. $value"345 fi346}347