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