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 7export LC_ALL=C 8 9MAX_RETRIES=600 10RETRY_INTERVAL=".1" # seconds 11SYSFS_KERNEL_DIR="/sys/kernel" 12SYSFS_KLP_DIR="$SYSFS_KERNEL_DIR/livepatch" 13SYSFS_DEBUG_DIR="$SYSFS_KERNEL_DIR/debug" 14SYSFS_KPROBES_DIR="$SYSFS_DEBUG_DIR/kprobes" 15if [[ -e /sys/kernel/tracing/trace ]]; then 16 SYSFS_TRACING_DIR="$SYSFS_KERNEL_DIR/tracing" 17else 18 SYSFS_TRACING_DIR="$SYSFS_DEBUG_DIR/tracing" 19fi 20 21# Kselftest framework requirement - SKIP code is 4 22ksft_skip=4 23 24# log(msg) - write message to kernel log 25# msg - insightful words 26function log() { 27 echo "$1" > /dev/kmsg 28} 29 30# skip(msg) - testing can't proceed 31# msg - explanation 32function skip() { 33 log "SKIP: $1" 34 echo "SKIP: $1" >&2 35 exit $ksft_skip 36} 37 38# root test 39function is_root() { 40 uid=$(id -u) 41 if [ $uid -ne 0 ]; then 42 echo "skip all tests: must be run as root" >&2 43 exit $ksft_skip 44 fi 45} 46 47# Check if we can compile the modules before loading them 48function has_kdir() { 49 if [ -z "$KDIR" ]; then 50 KDIR="/lib/modules/$(uname -r)/build" 51 fi 52 53 if [ ! -d "$KDIR" ]; then 54 echo "skip all tests: KDIR ($KDIR) not available to compile modules." 55 exit $ksft_skip 56 fi 57} 58 59# die(msg) - game over, man 60# msg - dying words 61function die() { 62 log "ERROR: $1" 63 echo "ERROR: $1" >&2 64 exit 1 65} 66 67function push_config() { 68 DYNAMIC_DEBUG=$(grep '^kernel/livepatch' "$SYSFS_DEBUG_DIR/dynamic_debug/control" | \ 69 awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}') 70 FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled) 71 KPROBE_ENABLED=$(cat "$SYSFS_KPROBES_DIR/enabled") 72 TRACING_ON=$(cat "$SYSFS_TRACING_DIR/tracing_on") 73 CURRENT_TRACER=$(cat "$SYSFS_TRACING_DIR/current_tracer") 74 FTRACE_FILTER=$(cat "$SYSFS_TRACING_DIR/set_ftrace_filter") 75} 76 77function pop_config() { 78 if [[ -n "$DYNAMIC_DEBUG" ]]; then 79 echo -n "$DYNAMIC_DEBUG" > "$SYSFS_DEBUG_DIR/dynamic_debug/control" 80 fi 81 if [[ -n "$FTRACE_ENABLED" ]]; then 82 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null 83 fi 84 if [[ -n "$KPROBE_ENABLED" ]]; then 85 echo "$KPROBE_ENABLED" > "$SYSFS_KPROBES_DIR/enabled" 86 fi 87 if [[ -n "$TRACING_ON" ]]; then 88 echo "$TRACING_ON" > "$SYSFS_TRACING_DIR/tracing_on" 89 fi 90 if [[ -n "$CURRENT_TRACER" ]]; then 91 echo "$CURRENT_TRACER" > "$SYSFS_TRACING_DIR/current_tracer" 92 fi 93 if [[ -n "$FTRACE_FILTER" ]]; then 94 echo "$FTRACE_FILTER" \ 95 | sed -e "/#### all functions enabled ####/d" \ 96 > "$SYSFS_TRACING_DIR/set_ftrace_filter" 97 fi 98} 99 100function set_dynamic_debug() { 101 cat <<-EOF > "$SYSFS_DEBUG_DIR/dynamic_debug/control" 102 file kernel/livepatch/* +p 103 func klp_try_switch_task -p 104 EOF 105} 106 107function set_ftrace_enabled() { 108 local can_fail=0 109 if [[ "$1" == "--fail" ]] ; then 110 can_fail=1 111 shift 112 fi 113 114 local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1) 115 local result=$(sysctl --values kernel.ftrace_enabled) 116 117 if [[ "$result" != "$1" ]] ; then 118 if [[ $can_fail -eq 1 ]] ; then 119 echo "livepatch: $err" | sed 's#/proc/sys/kernel/#kernel.#' > /dev/kmsg 120 return 121 fi 122 123 skip "failed to set kernel.ftrace_enabled = $1" 124 fi 125 126 echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg 127} 128 129function cleanup() { 130 pop_config 131} 132 133# setup_config - save the current config and set a script exit trap that 134# restores the original config. Setup the dynamic debug 135# for verbose livepatching output and turn on 136# the ftrace_enabled sysctl. 137function setup_config() { 138 is_root 139 has_kdir 140 push_config 141 set_dynamic_debug 142 set_ftrace_enabled 1 143 trap cleanup EXIT INT TERM HUP 144} 145 146# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 147# sleep $RETRY_INTERVAL between attempts 148# cmd - command and its arguments to run 149function loop_until() { 150 local cmd="$*" 151 local i=0 152 while true; do 153 eval "$cmd" && return 0 154 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 155 sleep $RETRY_INTERVAL 156 done 157} 158 159function is_livepatch_mod() { 160 local mod="$1" 161 162 if [[ ! -f "test_modules/$mod.ko" ]]; then 163 die "Can't find \"test_modules/$mod.ko\", try \"make\"" 164 fi 165 166 if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 167 return 0 168 fi 169 170 return 1 171} 172 173function __load_mod() { 174 local mod="$1"; shift 175 176 local msg="% insmod test_modules/$mod.ko $*" 177 log "${msg%% }" 178 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 179 if [[ "$ret" != "" ]]; then 180 die "$ret" 181 fi 182 183 # Wait for module in sysfs ... 184 loop_until '[[ -e "/sys/module/$mod" ]]' || 185 die "failed to load module $mod" 186} 187 188 189# load_mod(modname, params) - load a kernel module 190# modname - module name to load 191# params - module parameters to pass to insmod 192function load_mod() { 193 local mod="$1"; shift 194 195 is_livepatch_mod "$mod" && 196 die "use load_lp() to load the livepatch module $mod" 197 198 __load_mod "$mod" "$@" 199} 200 201# load_lp_nowait(modname, params) - load a kernel module with a livepatch 202# but do not wait on until the transition finishes 203# modname - module name to load 204# params - module parameters to pass to insmod 205function load_lp_nowait() { 206 local mod="$1"; shift 207 208 is_livepatch_mod "$mod" || 209 die "module $mod is not a livepatch" 210 211 __load_mod "$mod" "$@" 212 213 # Wait for livepatch in sysfs ... 214 loop_until '[[ -e "$SYSFS_KLP_DIR/$mod" ]]' || 215 die "failed to load module $mod (sysfs)" 216} 217 218# load_lp(modname, params) - load a kernel module with a livepatch 219# modname - module name to load 220# params - module parameters to pass to insmod 221function load_lp() { 222 local mod="$1"; shift 223 224 load_lp_nowait "$mod" "$@" 225 226 # Wait until the transition finishes ... 227 loop_until 'grep -q '^0$' $SYSFS_KLP_DIR/$mod/transition' || 228 die "failed to complete transition" 229} 230 231# load_failing_mod(modname, params) - load a kernel module, expect to fail 232# modname - module name to load 233# params - module parameters to pass to insmod 234function load_failing_mod() { 235 local mod="$1"; shift 236 237 local msg="% insmod test_modules/$mod.ko $*" 238 log "${msg%% }" 239 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 240 if [[ "$ret" == "" ]]; then 241 die "$mod unexpectedly loaded" 242 fi 243 log "$ret" 244} 245 246# unload_mod(modname) - unload a kernel module 247# modname - module name to unload 248function unload_mod() { 249 local mod="$1" 250 251 # Wait for module reference count to clear ... 252 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 253 die "failed to unload module $mod (refcnt)" 254 255 log "% rmmod $mod" 256 ret=$(rmmod "$mod" 2>&1) 257 if [[ "$ret" != "" ]]; then 258 die "$ret" 259 fi 260 261 # Wait for module in sysfs ... 262 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 263 die "failed to unload module $mod (/sys/module)" 264} 265 266# unload_lp(modname) - unload a kernel module with a livepatch 267# modname - module name to unload 268function unload_lp() { 269 unload_mod "$1" 270} 271 272# disable_lp(modname) - disable a livepatch 273# modname - module name to unload 274function disable_lp() { 275 local mod="$1" 276 277 log "% echo 0 > $SYSFS_KLP_DIR/$mod/enabled" 278 echo 0 > "$SYSFS_KLP_DIR/$mod/enabled" 279 280 # Wait until the transition finishes and the livepatch gets 281 # removed from sysfs... 282 loop_until '[[ ! -e "$SYSFS_KLP_DIR/$mod" ]]' || 283 die "failed to disable livepatch $mod" 284} 285 286# set_pre_patch_ret(modname, pre_patch_ret) 287# modname - module name to set 288# pre_patch_ret - new pre_patch_ret value 289function set_pre_patch_ret { 290 local mod="$1"; shift 291 local ret="$1" 292 293 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 294 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 295 296 # Wait for sysfs value to hold ... 297 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 298 die "failed to set pre_patch_ret parameter for $mod module" 299} 300 301function start_test { 302 local test="$1" 303 304 # Dump something unique into the dmesg log, then stash the entry 305 # in LAST_DMESG. The check_result() function will use it to 306 # find new kernel messages since the test started. 307 local last_dmesg_msg="livepatch kselftest timestamp: $(date --rfc-3339=ns)" 308 log "$last_dmesg_msg" 309 loop_until 'dmesg | grep -q "$last_dmesg_msg"' || 310 die "buffer busy? can't find canary dmesg message: $last_dmesg_msg" 311 LAST_DMESG=$(dmesg | grep "$last_dmesg_msg") 312 313 echo -n "TEST: $test ... " 314 log "===== TEST: $test =====" 315} 316 317# check_result() - verify dmesg output 318# TODO - better filter, out of order msgs, etc? 319function check_result { 320 local expect="$*" 321 local result 322 323 # Test results include any new dmesg entry since LAST_DMESG, then: 324 # - include lines matching keywords 325 # - exclude lines matching keywords 326 # - filter out dmesg timestamp prefixes 327 result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \ 328 grep -e 'livepatch:' -e 'test_klp' | \ 329 grep -v '\(tainting\|taints\) kernel' | \ 330 sed 's/^\[[ 0-9.]*\] //' | \ 331 sed 's/^\[[ ]*[CT][0-9]*\] //') 332 333 if [[ "$expect" == "$result" ]] ; then 334 echo "ok" 335 elif [[ "$result" == "" ]] ; then 336 echo -e "not ok\n\nbuffer overrun? can't find canary dmesg entry: $LAST_DMESG\n" 337 die "livepatch kselftest(s) failed" 338 else 339 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 340 die "livepatch kselftest(s) failed" 341 fi 342} 343 344# does_sysfs_exist(modname, attr) - check sysfs attribute existence 345# modname - livepatch module creating the sysfs interface 346# attr - attribute name to be checked 347function does_sysfs_exist() { 348 local mod="$1"; shift 349 local attr="$1"; shift 350 351 [[ -f "$SYSFS_KLP_DIR/$mod/$attr" ]] 352} 353 354# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs 355# path permissions 356# modname - livepatch module creating the sysfs interface 357# rel_path - relative path of the sysfs interface 358# expected_rights - expected access rights 359function check_sysfs_rights() { 360 local mod="$1"; shift 361 local rel_path="$1"; shift 362 local expected_rights="$1"; shift 363 364 local path="$SYSFS_KLP_DIR/$mod/$rel_path" 365 local rights=$(/bin/stat --format '%A' "$path") 366 if test "$rights" != "$expected_rights" ; then 367 die "Unexpected access rights of $path: $expected_rights vs. $rights" 368 fi 369} 370 371# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value 372# modname - livepatch module creating the sysfs interface 373# rel_path - relative path of the sysfs interface 374# expected_value - expected value read from the file 375function check_sysfs_value() { 376 local mod="$1"; shift 377 local rel_path="$1"; shift 378 local expected_value="$1"; shift 379 380 local path="$SYSFS_KLP_DIR/$mod/$rel_path" 381 local value=`cat $path` 382 if test "$value" != "$expected_value" ; then 383 die "Unexpected value in $path: $expected_value vs. $value" 384 fi 385} 386 387# cleanup_tracing() - stop and clean up function tracing 388function cleanup_tracing() { 389 echo 0 > "$SYSFS_TRACING_DIR/tracing_on" 390 echo "" > "$SYSFS_TRACING_DIR/set_ftrace_filter" 391 echo "nop" > "$SYSFS_TRACING_DIR/current_tracer" 392 echo "" > "$SYSFS_TRACING_DIR/trace" 393} 394 395# trace_function(function) - start tracing of a function 396# function - to be traced function 397function trace_function() { 398 local function="$1"; shift 399 400 cleanup_tracing 401 402 echo "function" > "$SYSFS_TRACING_DIR/current_tracer" 403 echo "$function" > "$SYSFS_TRACING_DIR/set_ftrace_filter" 404 echo 1 > "$SYSFS_TRACING_DIR/tracing_on" 405} 406 407# check_traced_functions(functions...) - check whether each function appeared in the trace log 408# functions - list of functions to be checked 409function check_traced_functions() { 410 local function 411 412 for function in "$@"; do 413 if ! grep -Fwq "$function" "$SYSFS_TRACING_DIR/trace" ; then 414 die "Function ($function) did not appear in the trace" 415 fi 416 done 417 418 cleanup_tracing 419} 420