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