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 129# ftrace_disable_supported() - probe whether kernel.ftrace_enabled=0 130# can still disable ftrace on this kernel. Newer kernels deprecate 131# the knob and always refuse the write with -EOPNOTSUPP. 132function ftrace_disable_supported() { 133 local orig result 134 135 orig=$(sysctl --values kernel.ftrace_enabled) 136 sysctl -q kernel.ftrace_enabled=0 &> /dev/null 137 result=$(sysctl --values kernel.ftrace_enabled) 138 sysctl -q "kernel.ftrace_enabled=$orig" &> /dev/null 139 140 [[ "$result" == "0" ]] 141} 142 143function cleanup() { 144 pop_config 145} 146 147# setup_config - save the current config and set a script exit trap that 148# restores the original config. Setup the dynamic debug 149# for verbose livepatching output and turn on 150# the ftrace_enabled sysctl. 151function setup_config() { 152 is_root 153 has_kdir 154 push_config 155 set_dynamic_debug 156 set_ftrace_enabled 1 157 trap cleanup EXIT INT TERM HUP 158} 159 160# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES, 161# sleep $RETRY_INTERVAL between attempts 162# cmd - command and its arguments to run 163function loop_until() { 164 local cmd="$*" 165 local i=0 166 while true; do 167 eval "$cmd" && return 0 168 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1 169 sleep $RETRY_INTERVAL 170 done 171} 172 173function is_livepatch_mod() { 174 local mod="$1" 175 176 if [[ ! -f "test_modules/$mod.ko" ]]; then 177 die "Can't find \"test_modules/$mod.ko\", try \"make\"" 178 fi 179 180 if [[ $(modinfo "test_modules/$mod.ko" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then 181 return 0 182 fi 183 184 return 1 185} 186 187function __load_mod() { 188 local mod="$1"; shift 189 190 local msg="% insmod test_modules/$mod.ko $*" 191 log "${msg%% }" 192 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 193 if [[ "$ret" != "" ]]; then 194 die "$ret" 195 fi 196 197 # Wait for module in sysfs ... 198 loop_until '[[ -e "/sys/module/$mod" ]]' || 199 die "failed to load module $mod" 200} 201 202 203# load_mod(modname, params) - load a kernel module 204# modname - module name to load 205# params - module parameters to pass to insmod 206function load_mod() { 207 local mod="$1"; shift 208 209 is_livepatch_mod "$mod" && 210 die "use load_lp() to load the livepatch module $mod" 211 212 __load_mod "$mod" "$@" 213} 214 215# load_lp_nowait(modname, params) - load a kernel module with a livepatch 216# but do not wait on until the transition finishes 217# modname - module name to load 218# params - module parameters to pass to insmod 219function load_lp_nowait() { 220 local mod="$1"; shift 221 222 is_livepatch_mod "$mod" || 223 die "module $mod is not a livepatch" 224 225 __load_mod "$mod" "$@" 226 227 # Wait for livepatch in sysfs ... 228 loop_until '[[ -e "$SYSFS_KLP_DIR/$mod" ]]' || 229 die "failed to load module $mod (sysfs)" 230} 231 232# load_lp(modname, params) - load a kernel module with a livepatch 233# modname - module name to load 234# params - module parameters to pass to insmod 235function load_lp() { 236 local mod="$1"; shift 237 238 load_lp_nowait "$mod" "$@" 239 240 # Wait until the transition finishes ... 241 loop_until 'grep -q '^0$' $SYSFS_KLP_DIR/$mod/transition' || 242 die "failed to complete transition" 243} 244 245# load_failing_mod(modname, params) - load a kernel module, expect to fail 246# modname - module name to load 247# params - module parameters to pass to insmod 248function load_failing_mod() { 249 local mod="$1"; shift 250 251 local msg="% insmod test_modules/$mod.ko $*" 252 log "${msg%% }" 253 ret=$(insmod "test_modules/$mod.ko" "$@" 2>&1) 254 if [[ "$ret" == "" ]]; then 255 die "$mod unexpectedly loaded" 256 fi 257 log "$ret" 258} 259 260# unload_mod(modname) - unload a kernel module 261# modname - module name to unload 262function unload_mod() { 263 local mod="$1" 264 265 # Wait for module reference count to clear ... 266 loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' || 267 die "failed to unload module $mod (refcnt)" 268 269 log "% rmmod $mod" 270 ret=$(rmmod "$mod" 2>&1) 271 if [[ "$ret" != "" ]]; then 272 die "$ret" 273 fi 274 275 # Wait for module in sysfs ... 276 loop_until '[[ ! -e "/sys/module/$mod" ]]' || 277 die "failed to unload module $mod (/sys/module)" 278} 279 280# unload_lp(modname) - unload a kernel module with a livepatch 281# modname - module name to unload 282function unload_lp() { 283 unload_mod "$1" 284} 285 286# disable_lp(modname) - disable a livepatch 287# modname - module name to unload 288function disable_lp() { 289 local mod="$1" 290 291 log "% echo 0 > $SYSFS_KLP_DIR/$mod/enabled" 292 echo 0 > "$SYSFS_KLP_DIR/$mod/enabled" 293 294 # Wait until the transition finishes and the livepatch gets 295 # removed from sysfs... 296 loop_until '[[ ! -e "$SYSFS_KLP_DIR/$mod" ]]' || 297 die "failed to disable livepatch $mod" 298} 299 300# set_pre_patch_ret(modname, pre_patch_ret) 301# modname - module name to set 302# pre_patch_ret - new pre_patch_ret value 303function set_pre_patch_ret { 304 local mod="$1"; shift 305 local ret="$1" 306 307 log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret" 308 echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret 309 310 # Wait for sysfs value to hold ... 311 loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' || 312 die "failed to set pre_patch_ret parameter for $mod module" 313} 314 315function start_test { 316 local test="$1" 317 318 # Dump something unique into the dmesg log, then stash the entry 319 # in LAST_DMESG. The check_result() function will use it to 320 # find new kernel messages since the test started. 321 local last_dmesg_msg="livepatch kselftest timestamp: $(date --rfc-3339=ns)" 322 log "$last_dmesg_msg" 323 loop_until 'dmesg | grep -q "$last_dmesg_msg"' || 324 die "buffer busy? can't find canary dmesg message: $last_dmesg_msg" 325 LAST_DMESG=$(dmesg | grep "$last_dmesg_msg") 326 327 echo -n "TEST: $test ... " 328 log "===== TEST: $test =====" 329} 330 331# check_result() - verify dmesg output 332# TODO - better filter, out of order msgs, etc? 333function check_result { 334 local expect="$*" 335 local result 336 337 # Test results include any new dmesg entry since LAST_DMESG, then: 338 # - include lines matching keywords 339 # - exclude lines matching keywords 340 # - filter out dmesg timestamp prefixes 341 result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \ 342 grep -e 'livepatch:' -e 'test_klp' | \ 343 grep -v '\(tainting\|taints\) kernel' | \ 344 sed 's/^\[[ 0-9.]*\] //' | \ 345 sed 's/^\[[ ]*[CT][0-9]*\] //') 346 347 if [[ "$expect" == "$result" ]] ; then 348 echo "ok" 349 elif [[ "$result" == "" ]] ; then 350 echo -e "not ok\n\nbuffer overrun? can't find canary dmesg entry: $LAST_DMESG\n" 351 die "livepatch kselftest(s) failed" 352 else 353 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n" 354 die "livepatch kselftest(s) failed" 355 fi 356} 357 358# does_sysfs_exist(modname, attr) - check sysfs attribute existence 359# modname - livepatch module creating the sysfs interface 360# attr - attribute name to be checked 361function does_sysfs_exist() { 362 local mod="$1"; shift 363 local attr="$1"; shift 364 365 [[ -f "$SYSFS_KLP_DIR/$mod/$attr" ]] 366} 367 368# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs 369# path permissions 370# modname - livepatch module creating the sysfs interface 371# rel_path - relative path of the sysfs interface 372# expected_rights - expected access rights 373function check_sysfs_rights() { 374 local mod="$1"; shift 375 local rel_path="$1"; shift 376 local expected_rights="$1"; shift 377 378 local path="$SYSFS_KLP_DIR/$mod/$rel_path" 379 local rights=$(/bin/stat --format '%A' "$path") 380 if test "$rights" != "$expected_rights" ; then 381 die "Unexpected access rights of $path: $expected_rights vs. $rights" 382 fi 383} 384 385# check_sysfs_value(modname, rel_path, expected_value) - check sysfs value 386# modname - livepatch module creating the sysfs interface 387# rel_path - relative path of the sysfs interface 388# expected_value - expected value read from the file 389function check_sysfs_value() { 390 local mod="$1"; shift 391 local rel_path="$1"; shift 392 local expected_value="$1"; shift 393 394 local path="$SYSFS_KLP_DIR/$mod/$rel_path" 395 local value=`cat $path` 396 if test "$value" != "$expected_value" ; then 397 die "Unexpected value in $path: $expected_value vs. $value" 398 fi 399} 400 401# cleanup_tracing() - stop and clean up function tracing 402function cleanup_tracing() { 403 echo 0 > "$SYSFS_TRACING_DIR/tracing_on" 404 echo "" > "$SYSFS_TRACING_DIR/set_ftrace_filter" 405 echo "nop" > "$SYSFS_TRACING_DIR/current_tracer" 406 echo "" > "$SYSFS_TRACING_DIR/trace" 407} 408 409# trace_function(function) - start tracing of a function 410# function - to be traced function 411function trace_function() { 412 local function="$1"; shift 413 414 cleanup_tracing 415 416 echo "function" > "$SYSFS_TRACING_DIR/current_tracer" 417 echo "$function" > "$SYSFS_TRACING_DIR/set_ftrace_filter" 418 echo 1 > "$SYSFS_TRACING_DIR/tracing_on" 419} 420 421# check_traced_functions(functions...) - check whether each function appeared in the trace log 422# functions - list of functions to be checked 423function check_traced_functions() { 424 local function 425 426 for function in "$@"; do 427 if ! grep -Fwq "$function" "$SYSFS_TRACING_DIR/trace" ; then 428 die "Function ($function) did not appear in the trace" 429 fi 430 done 431 432 cleanup_tracing 433} 434