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