-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathklp_tc_functions.sh
382 lines (319 loc) · 10.1 KB
/
klp_tc_functions.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# Copyright (C) 2017 SUSE
# Authors: Lance Wang, Libor Pechacek
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
# define useful variables
readonly SOURCE_DIR="$(dirname $0)"
function __klp_add_patched_func() {
local PATCH_ID="$1"
local FUNC="$2"
echo -n "\t{\n"
echo -n "\t\t.old_name = \"orig_${FUNC}\",\n"
echo -n "\t\t.new_func = klp_${PATCH_ID}_${FUNC},\n"
echo -n "\t},\n"
}
function klp_create_header() {
sed "s%@@USE_OLD_HRTIMER_API@@%$KLP_TEST_HRTIMER_OLD%" \
"${SOURCE_DIR}/klp_test_support_mod.h" \
> "${OUTPUT_DIR}/klp_test_support_mod.h"
}
# Create a livepatch source file from template
# Parameters:
# - output file name
# - livepatch id
# - replace-all: either true or false
# - list of to be patched functions
function klp_create_patch_module_src() {
local TEMPLATE="${SOURCE_DIR}/klp_test_livepatch.c"
local SRC_FILE="$1"
local PATCH_ID="$2"
local PATCH_REPLACE_ALL="$3"
shift 3
local OUTPUT_DIR="$(dirname $SRC_FILE)"
PATCH_FUNCS=""
PATCH_GETPID=0
while [ $# -gt 0 ]; do
local FUNC="$1"
shift
if [ x"$FUNC" == x${KLP_TEST_SYSCALL_FN_PREFIX}sys_getpid ]; then
PATCH_GETPID=1
continue
fi
PATCH_FUNCS="${PATCH_FUNCS}$(__klp_add_patched_func $PATCH_ID $FUNC)"
done
mkdir -p "$(dirname "$SRC_FILE")"
sed -f - "$TEMPLATE" > "${SRC_FILE}.tmp" <<EOF
s%@@PATCH_ID@@%$PATCH_ID%;
s%@@PATCH_GETPID@@%$PATCH_GETPID%;
s%@@SYSCALL_FN_PREFIX@@%$KLP_TEST_SYSCALL_FN_PREFIX%;
s%@@PATCH_REPLACE_ALL@@%$PATCH_REPLACE_ALL%;
s%@@PATCH_FUNCS@@%$PATCH_FUNCS%;
s%@@USE_OLD_REG_API@@%$KLP_TEST_USE_OLD_REG_API%;
EOF
if [ ! -e "${SRC_FILE}" ] || \
! diff "${SRC_FILE}" "${SRC_FILE}.tmp" > /dev/null 2>&1; then
mv "${SRC_FILE}.tmp" "${SRC_FILE}"
else
rm "${SRC_FILE}.tmp"
fi
klp_create_header
}
# Compile a kernel module
# parameters: source file
function klp_compile_module() {
local SRC_FILE="$1"
local OUTPUT_DIR="$(dirname "$1")"
echo "obj-m += " $(basename "$SRC_FILE" .c)".o" \
> "$OUTPUT_DIR"/Makefile
make -C /lib/modules/$(uname -r)/build \
M="$OUTPUT_DIR" modules 1>&2
if [ $? -ne 0 ]; then
return 1
fi
echo "${SRC_FILE%.c}.ko"
}
# Create a livepatch module from template
# Parameters:
# - optional -r: create "replace-all" live patch
# - optional -o <dir>: output directory (default: /tmp/live-patch/<patch-id>)
# - livepatch id
# - list of to be patched functions
function klp_create_patch_module() {
local REPLACE_ALL=false
local OUTPUT_DIR
local OPTIND=1
local OPTARG
local O
while getopts ':ro:' O; do
case "$O" in
'r')
REPLACE_ALL=true
;;
'o')
OUTPUT_DIR="$OPTARG"
;;
'?')
echo -n "error: klp_create_patch_module: invalid parameter" 1>&2
echo " '$OPTARG'" 1>&2
return 1
;;
esac
done
shift $((OPTIND-1))
local PATCH_ID="$1"
shift
OUTPUT_DIR="${OUTPUT_DIR:-/tmp/live-patch/${PATCH_ID}}"
local SRC_FILE="${OUTPUT_DIR}/klp_${PATCH_ID}_livepatch.c"
klp_create_patch_module_src "$SRC_FILE" "$PATCH_ID" "$REPLACE_ALL" "$@"
klp_compile_module "$SRC_FILE"
}
function klp_create_test_support_module() {
local OUTPUT_DIR="$1"
klp_create_header
cp -u "${SOURCE_DIR}/klp_test_support_mod.c" "${OUTPUT_DIR}/"
klp_compile_module "${OUTPUT_DIR}/klp_test_support_mod.c"
}
function klp_prepare_test_support_module() {
local OUTPUT_DIR="$1"
klp_tc_milestone "Compile test support module"
local SUPPORT_KO="$(klp_create_test_support_module "$OUTPUT_DIR")"
if [ $? -ne 0 ]; then
return 1
fi
klp_tc_milestone "Load test support module"
insmod "$SUPPORT_KO"
if [ $? -ne 0 ]; then
return 1
fi
register_mod_for_unload "$(basename $SUPPORT_KO .ko)"
}
function klp_in_progress() {
for p in /sys/kernel/livepatch/*; do
[ 0$(cat "$p/transition" 2>/dev/null) -ne 0 ] && return 0
done
return 1
}
function klp_wait_complete() {
local PATCH_MOD_NAME="$1"
if [ $# -gt 1 ]; then
TIMEOUT=$2
else
TIMEOUT=-1
fi
while klp_in_progress && [ $TIMEOUT -ne 0 ]; do
sleep 1
(( TIMEOUT-- )) || true
done
if klp_in_progress; then
return 1
fi
# See if the livepatch in question had been disabled and wait for
# the module's reference count to drop asynchronously to zero if
# so.
if [ 0"$(cat "/sys/kernel/livepatch/$PATCH_MOD_NAME/enabled" 2> /dev/null)" -ne 0 ]; then
return 0
fi
while [ 0"$(cat "/sys/module/$PATCH_MOD_NAME/refcnt")" -ne 0 -a \
$TIMEOUT -ne 0 ]; do
sleep 1
(( TIMEOUT-- )) || true
done
[ 0"$(cat "/sys/module/$PATCH_MOD_NAME/refcnt")" -eq 0 ]
}
function klp_dump_blocking_processes() {
unset PIDS
TRANSITIONING_PATCH="$(grep -l '^1$' /sys/kernel/livepatch/*/transition | head -n1)"
echo "transitioning patch path: \"${TRANSITIONING_PATCH%/transition}\""
if [ -n "$TRANSITIONING_PATCH" ]; then
TRANSITION_DIRECTION=$(cat "${TRANSITIONING_PATCH/%\/transition/\/enabled}")
for DIR in /proc/[0-9]*/task/[0-9]*; do
PATCH_STATE=$(cat $DIR/patch_state 2>/dev/null)
if [ -n "$PATCH_STATE" ] && [ "$PATCH_STATE" -ge 0 \
-a "$PATCH_STATE" -ne "$TRANSITION_DIRECTION" ]; then
PID=${DIR#/proc/}
PID=${PID%/task/*}
TID=${DIR#*/task/}
COMM="$(cat $DIR/comm)"
echo "$COMM ($PID/$TID) still in progress:"
cat $DIR/stack
echo -e '=============\n'
PIDS="$PIDS $PID"
fi
done
fi
if [ -z "$PIDS" ]; then
echo "no threads with klp_in_progress set"
fi
}
declare -a RECOVERY_HOOKS
function push_recovery_fn() {
[ -z "$1" ] && echo "WARNING: no parameters passed to push_recovery_fn"
RECOVERY_HOOKS[${#RECOVERY_HOOKS[*]}]="$1"
}
function pop_and_run_recovery_fn() {
local fn=$1
local num_hook=${#RECOVERY_HOOKS[*]}
[ $num_hook -eq 0 ] && return 1
(( num_hook--)) || true
eval ${RECOVERY_HOOKS[$num_hook]} || true
unset RECOVERY_HOOKS[$num_hook]
return 0
}
function call_recovery_hooks() {
for fn in "${RECOVERY_HOOKS[@]}"; do
echo "calling \"$fn\""
eval $fn || true
done
}
function klp_tc_write() {
logger "$*"
echo "$*"
}
function klp_tc_cleanup() {
klp_tc_milestone "Removing patches"
for P in ${MODULES_LOADED[@]}; do
if [ -d /sys/kernel/livepatch/"$P" ]; then
klp_tc_milestone "Disabling and removing module $P"
echo 0 > /sys/kernel/livepatch/"$P"/enabled
if ! klp_wait_complete "$P" 61; then
klp_dump_blocking_processes
klp_tc_abort "module deactivation didn't finish in time"
fi
else
klp_tc_milestone "Removing module $P"
fi
if ! rmmod "$P"; then
dmesg | tail -30
klp_tc_abort "module removal failed"
fi
done
}
function klp_tc_init() {
trap "ret=\$?; [ \$ret -ne 0 ] && call_recovery_hooks; klp_tc_cleanup; result=FAILED; [ \$ret -eq 22 ] && result=SKIPPED; echo TEST \$result while executing \'\$BASH_COMMAND\'" EXIT
# timestamp every line of output
exec > >(awk '{ print strftime("[%T] ") $0 }')
exec 2>&1
klp_tc_write "$1"
if klp_in_progress; then
klp_tc_write "ERROR kernel live patching in progress, cannot start test"
exit 22 # means SKIPPED in CTCS2 terminology
fi
}
declare -a MODULES_LOADED
function register_mod_for_unload() {
[ -z "$1" ] && echo "WARNING: no parameters passed to register_mod_for_unload"
MODULES_LOADED=("$1" ${MODULES_LOADED[@]})
}
function klp_tc_exit() {
trap - EXIT
klp_tc_cleanup
klp_tc_milestone "TEST PASSED"
}
function klp_tc_milestone() {
klp_tc_write "***" "$*"
}
function klp_tc_abort() {
local ret=$?
local result="ABORT"
[ $ret -eq 22 ] && result="SKIPPED"
klp_tc_write "TEST CASE $result" "$*"
exit $ret
}
# detect environment settings
KLP_ENV_CACHE_FILE=/tmp/live-patch/klp_env_cache
if [ ! -f $KLP_ENV_CACHE_FILE ]; then
mkdir -p $(dirname $KLP_ENV_CACHE_FILE)
# compile-test for hrtimer API ()
COMPILETEST_DIR=/tmp/live-patch/klp_compile_test
mkdir -p $COMPILETEST_DIR
cp "$SOURCE_DIR"/klp_compile_test_hrtimer.c $COMPILETEST_DIR/
echo -n 'export KLP_TEST_HRTIMER_OLD=' > $KLP_ENV_CACHE_FILE
if klp_compile_module $COMPILETEST_DIR/klp_compile_test_hrtimer.c > /dev/null 2>&1;
then
echo "1" >> $KLP_ENV_CACHE_FILE
else
echo "0" >> $KLP_ENV_CACHE_FILE
fi
# Check for getpid syscall prefix
echo -n 'export KLP_TEST_SYSCALL_FN_PREFIX=' >> $KLP_ENV_CACHE_FILE
# generate LINUX_VERSION_CODE from `uname -r`
KVER="$(uname -r | awk -F'[-+]' '{print $1}')"
PART1="$(echo $KVER | cut -d. -f1)"
PART2="$(echo $KVER | cut -d. -f2)"
PART3="$(echo $KVER | cut -d. -f3)"
VERSION_CODE=$(((PART1 <<16) + (PART2 <<8) + PART3))
if [ "$VERSION_CODE" -ge 266496 ] # test for kernel 4.17.0 and newer
then
case $(uname -m) in
x86_64) echo "__x64_" >> $KLP_ENV_CACHE_FILE
;;
s390x) echo "__s390x_" >> $KLP_ENV_CACHE_FILE
;;
aarch64) echo "__arm64_" >> $KLP_ENV_CACHE_FILE
;;
# CONFIG_ARCH_HAS_SYSCALL_WRAPPER is not set for ppc64le
ppc64le) echo >> $KLP_ENV_CACHE_FILE
;;
*) klp_tc_abort "Arch $(uname -m) not supported"
;;
esac
else
echo >> $KLP_ENV_CACHE_FILE
fi
# Decide whether to use simplified KLP API (kernel commit 958ef1e39d24)
# based on presence of an obsolete API function
echo -n 'export KLP_TEST_USE_OLD_REG_API=' >> $KLP_ENV_CACHE_FILE
( grep -q 'T klp_register_patch$' /proc/kallsyms && echo "1" || echo "0" ) >> $KLP_ENV_CACHE_FILE
fi
. $KLP_ENV_CACHE_FILE