Back to home page

OSCL-LXR

 
 

    


0001 #!/bin/bash
0002 # SPDX-License-Identifier: GPL-2.0
0003 # Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
0004 
0005 # Shell functions for the rest of the scripts.
0006 
0007 MAX_RETRIES=600
0008 RETRY_INTERVAL=".1"     # seconds
0009 
0010 # Kselftest framework requirement - SKIP code is 4
0011 ksft_skip=4
0012 
0013 # log(msg) - write message to kernel log
0014 #       msg - insightful words
0015 function log() {
0016         echo "$1" > /dev/kmsg
0017 }
0018 
0019 # skip(msg) - testing can't proceed
0020 #       msg - explanation
0021 function skip() {
0022         log "SKIP: $1"
0023         echo "SKIP: $1" >&2
0024         exit $ksft_skip
0025 }
0026 
0027 # root test
0028 function is_root() {
0029         uid=$(id -u)
0030         if [ $uid -ne 0 ]; then
0031                 echo "skip all tests: must be run as root" >&2
0032                 exit $ksft_skip
0033         fi
0034 }
0035 
0036 # die(msg) - game over, man
0037 #       msg - dying words
0038 function die() {
0039         log "ERROR: $1"
0040         echo "ERROR: $1" >&2
0041         exit 1
0042 }
0043 
0044 # save existing dmesg so we can detect new content
0045 function save_dmesg() {
0046         SAVED_DMESG=$(mktemp --tmpdir -t klp-dmesg-XXXXXX)
0047         dmesg > "$SAVED_DMESG"
0048 }
0049 
0050 # cleanup temporary dmesg file from save_dmesg()
0051 function cleanup_dmesg_file() {
0052         rm -f "$SAVED_DMESG"
0053 }
0054 
0055 function push_config() {
0056         DYNAMIC_DEBUG=$(grep '^kernel/livepatch' /sys/kernel/debug/dynamic_debug/control | \
0057                         awk -F'[: ]' '{print "file " $1 " line " $2 " " $4}')
0058         FTRACE_ENABLED=$(sysctl --values kernel.ftrace_enabled)
0059 }
0060 
0061 function pop_config() {
0062         if [[ -n "$DYNAMIC_DEBUG" ]]; then
0063                 echo -n "$DYNAMIC_DEBUG" > /sys/kernel/debug/dynamic_debug/control
0064         fi
0065         if [[ -n "$FTRACE_ENABLED" ]]; then
0066                 sysctl kernel.ftrace_enabled="$FTRACE_ENABLED" &> /dev/null
0067         fi
0068 }
0069 
0070 function set_dynamic_debug() {
0071         cat <<-EOF > /sys/kernel/debug/dynamic_debug/control
0072                 file kernel/livepatch/* +p
0073                 func klp_try_switch_task -p
0074                 EOF
0075 }
0076 
0077 function set_ftrace_enabled() {
0078         local can_fail=0
0079         if [[ "$1" == "--fail" ]] ; then
0080                 can_fail=1
0081                 shift
0082         fi
0083 
0084         local err=$(sysctl -q kernel.ftrace_enabled="$1" 2>&1)
0085         local result=$(sysctl --values kernel.ftrace_enabled)
0086 
0087         if [[ "$result" != "$1" ]] ; then
0088                 if [[ $can_fail -eq 1 ]] ; then
0089                         echo "livepatch: $err" > /dev/kmsg
0090                         return
0091                 fi
0092 
0093                 skip "failed to set kernel.ftrace_enabled = $1"
0094         fi
0095 
0096         echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
0097 }
0098 
0099 function cleanup() {
0100         pop_config
0101         cleanup_dmesg_file
0102 }
0103 
0104 # setup_config - save the current config and set a script exit trap that
0105 #                restores the original config.  Setup the dynamic debug
0106 #                for verbose livepatching output and turn on
0107 #                the ftrace_enabled sysctl.
0108 function setup_config() {
0109         is_root
0110         push_config
0111         set_dynamic_debug
0112         set_ftrace_enabled 1
0113         trap cleanup EXIT INT TERM HUP
0114 }
0115 
0116 # loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
0117 #                   sleep $RETRY_INTERVAL between attempts
0118 #       cmd - command and its arguments to run
0119 function loop_until() {
0120         local cmd="$*"
0121         local i=0
0122         while true; do
0123                 eval "$cmd" && return 0
0124                 [[ $((i++)) -eq $MAX_RETRIES ]] && return 1
0125                 sleep $RETRY_INTERVAL
0126         done
0127 }
0128 
0129 function assert_mod() {
0130         local mod="$1"
0131 
0132         modprobe --dry-run "$mod" &>/dev/null
0133 }
0134 
0135 function is_livepatch_mod() {
0136         local mod="$1"
0137 
0138         if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
0139                 return 0
0140         fi
0141 
0142         return 1
0143 }
0144 
0145 function __load_mod() {
0146         local mod="$1"; shift
0147 
0148         local msg="% modprobe $mod $*"
0149         log "${msg%% }"
0150         ret=$(modprobe "$mod" "$@" 2>&1)
0151         if [[ "$ret" != "" ]]; then
0152                 die "$ret"
0153         fi
0154 
0155         # Wait for module in sysfs ...
0156         loop_until '[[ -e "/sys/module/$mod" ]]' ||
0157                 die "failed to load module $mod"
0158 }
0159 
0160 
0161 # load_mod(modname, params) - load a kernel module
0162 #       modname - module name to load
0163 #       params  - module parameters to pass to modprobe
0164 function load_mod() {
0165         local mod="$1"; shift
0166 
0167         assert_mod "$mod" ||
0168                 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
0169 
0170         is_livepatch_mod "$mod" &&
0171                 die "use load_lp() to load the livepatch module $mod"
0172 
0173         __load_mod "$mod" "$@"
0174 }
0175 
0176 # load_lp_nowait(modname, params) - load a kernel module with a livepatch
0177 #                       but do not wait on until the transition finishes
0178 #       modname - module name to load
0179 #       params  - module parameters to pass to modprobe
0180 function load_lp_nowait() {
0181         local mod="$1"; shift
0182 
0183         assert_mod "$mod" ||
0184                 skip "unable to load module ${mod}, verify CONFIG_TEST_LIVEPATCH=m and run self-tests as root"
0185 
0186         is_livepatch_mod "$mod" ||
0187                 die "module $mod is not a livepatch"
0188 
0189         __load_mod "$mod" "$@"
0190 
0191         # Wait for livepatch in sysfs ...
0192         loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
0193                 die "failed to load module $mod (sysfs)"
0194 }
0195 
0196 # load_lp(modname, params) - load a kernel module with a livepatch
0197 #       modname - module name to load
0198 #       params  - module parameters to pass to modprobe
0199 function load_lp() {
0200         local mod="$1"; shift
0201 
0202         load_lp_nowait "$mod" "$@"
0203 
0204         # Wait until the transition finishes ...
0205         loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
0206                 die "failed to complete transition"
0207 }
0208 
0209 # load_failing_mod(modname, params) - load a kernel module, expect to fail
0210 #       modname - module name to load
0211 #       params  - module parameters to pass to modprobe
0212 function load_failing_mod() {
0213         local mod="$1"; shift
0214 
0215         local msg="% modprobe $mod $*"
0216         log "${msg%% }"
0217         ret=$(modprobe "$mod" "$@" 2>&1)
0218         if [[ "$ret" == "" ]]; then
0219                 die "$mod unexpectedly loaded"
0220         fi
0221         log "$ret"
0222 }
0223 
0224 # unload_mod(modname) - unload a kernel module
0225 #       modname - module name to unload
0226 function unload_mod() {
0227         local mod="$1"
0228 
0229         # Wait for module reference count to clear ...
0230         loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
0231                 die "failed to unload module $mod (refcnt)"
0232 
0233         log "% rmmod $mod"
0234         ret=$(rmmod "$mod" 2>&1)
0235         if [[ "$ret" != "" ]]; then
0236                 die "$ret"
0237         fi
0238 
0239         # Wait for module in sysfs ...
0240         loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
0241                 die "failed to unload module $mod (/sys/module)"
0242 }
0243 
0244 # unload_lp(modname) - unload a kernel module with a livepatch
0245 #       modname - module name to unload
0246 function unload_lp() {
0247         unload_mod "$1"
0248 }
0249 
0250 # disable_lp(modname) - disable a livepatch
0251 #       modname - module name to unload
0252 function disable_lp() {
0253         local mod="$1"
0254 
0255         log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
0256         echo 0 > /sys/kernel/livepatch/"$mod"/enabled
0257 
0258         # Wait until the transition finishes and the livepatch gets
0259         # removed from sysfs...
0260         loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
0261                 die "failed to disable livepatch $mod"
0262 }
0263 
0264 # set_pre_patch_ret(modname, pre_patch_ret)
0265 #       modname - module name to set
0266 #       pre_patch_ret - new pre_patch_ret value
0267 function set_pre_patch_ret {
0268         local mod="$1"; shift
0269         local ret="$1"
0270 
0271         log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
0272         echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
0273 
0274         # Wait for sysfs value to hold ...
0275         loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
0276                 die "failed to set pre_patch_ret parameter for $mod module"
0277 }
0278 
0279 function start_test {
0280         local test="$1"
0281 
0282         save_dmesg
0283         echo -n "TEST: $test ... "
0284         log "===== TEST: $test ====="
0285 }
0286 
0287 # check_result() - verify dmesg output
0288 #       TODO - better filter, out of order msgs, etc?
0289 function check_result {
0290         local expect="$*"
0291         local result
0292 
0293         # Note: when comparing dmesg output, the kernel log timestamps
0294         # help differentiate repeated testing runs.  Remove them with a
0295         # post-comparison sed filter.
0296 
0297         result=$(dmesg | comm --nocheck-order -13 "$SAVED_DMESG" - | \
0298                  grep -e 'livepatch:' -e 'test_klp' | \
0299                  grep -v '\(tainting\|taints\) kernel' | \
0300                  sed 's/^\[[ 0-9.]*\] //')
0301 
0302         if [[ "$expect" == "$result" ]] ; then
0303                 echo "ok"
0304         else
0305                 echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
0306                 die "livepatch kselftest(s) failed"
0307         fi
0308 
0309         cleanup_dmesg_file
0310 }