#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>

. $(dirname $0)/functions.sh

MOD_LIVEPATCH1=test_klp_livepatch
MOD_LIVEPATCH2=test_klp_syscall
MOD_LIVEPATCH3=test_klp_callbacks_demo
MOD_REPLACE=test_klp_atomic_replace
MOD_TARGET=test_klp_mod_target
MOD_TARGET_PATCH=test_klp_mod_patch

setup_config


# - load a livepatch that modifies the output from /proc/cmdline and
#   verify correct behavior
# - unload the livepatch and make sure the patch was removed

start_test "basic function patching"

load_lp $MOD_LIVEPATCH1

if [[ "$(cat /proc/cmdline)" != "$MOD_LIVEPATCH1: this has been live patched" ]] ; then
	echo -e "FAIL\n\n"
	die "livepatch kselftest(s) failed"
fi

disable_lp $MOD_LIVEPATCH1
unload_lp $MOD_LIVEPATCH1

if [[ "$(cat /proc/cmdline)" == "$MOD_LIVEPATCH1: this has been live patched" ]] ; then
	echo -e "FAIL\n\n"
	die "livepatch kselftest(s) failed"
fi

check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko
livepatch: enabling patch '$MOD_LIVEPATCH1'
livepatch: '$MOD_LIVEPATCH1': initializing patching transition
livepatch: '$MOD_LIVEPATCH1': starting patching transition
livepatch: '$MOD_LIVEPATCH1': completing patching transition
livepatch: '$MOD_LIVEPATCH1': patching complete
% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH1/enabled
livepatch: '$MOD_LIVEPATCH1': initializing unpatching transition
livepatch: '$MOD_LIVEPATCH1': starting unpatching transition
livepatch: '$MOD_LIVEPATCH1': completing unpatching transition
livepatch: '$MOD_LIVEPATCH1': unpatching complete
% rmmod $MOD_LIVEPATCH1"


# - load a livepatch that modifies the output from /proc/cmdline and
#   verify correct behavior
# - load another livepatch and verify that both livepatches are active
# - unload the second livepatch and verify that the first is still active
# - unload the first livepatch and verify none are active

start_test "multiple livepatches"

load_lp $MOD_LIVEPATCH1

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

load_lp $MOD_REPLACE replace=0

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

disable_lp $MOD_REPLACE
unload_lp $MOD_REPLACE

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

disable_lp $MOD_LIVEPATCH1
unload_lp $MOD_LIVEPATCH1

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko
livepatch: enabling patch '$MOD_LIVEPATCH1'
livepatch: '$MOD_LIVEPATCH1': initializing patching transition
livepatch: '$MOD_LIVEPATCH1': starting patching transition
livepatch: '$MOD_LIVEPATCH1': completing patching transition
livepatch: '$MOD_LIVEPATCH1': patching complete
$MOD_LIVEPATCH1: this has been live patched
% insmod test_modules/$MOD_REPLACE.ko replace=0
livepatch: enabling patch '$MOD_REPLACE'
livepatch: '$MOD_REPLACE': initializing patching transition
livepatch: '$MOD_REPLACE': starting patching transition
livepatch: '$MOD_REPLACE': completing patching transition
livepatch: '$MOD_REPLACE': patching complete
$MOD_LIVEPATCH1: this has been live patched
$MOD_REPLACE: this has been live patched
% echo 0 > $SYSFS_KLP_DIR/$MOD_REPLACE/enabled
livepatch: '$MOD_REPLACE': initializing unpatching transition
livepatch: '$MOD_REPLACE': starting unpatching transition
livepatch: '$MOD_REPLACE': completing unpatching transition
livepatch: '$MOD_REPLACE': unpatching complete
% rmmod $MOD_REPLACE
$MOD_LIVEPATCH1: this has been live patched
% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH1/enabled
livepatch: '$MOD_LIVEPATCH1': initializing unpatching transition
livepatch: '$MOD_LIVEPATCH1': starting unpatching transition
livepatch: '$MOD_LIVEPATCH1': completing unpatching transition
livepatch: '$MOD_LIVEPATCH1': unpatching complete
% rmmod $MOD_LIVEPATCH1"


# - load a livepatch that modifies the output from /proc/cmdline and
#   verify correct behavior
# - load two additional livepatches and check the number of livepatch modules
#   applied
# - load an atomic replace livepatch and check that the other three modules were
#   disabled
# - remove all livepatches besides the atomic replace one and verify that the
#   atomic replace livepatch is still active
# - remove the atomic replace livepatch and verify that none are active

start_test "atomic replace livepatch"

load_lp $MOD_LIVEPATCH1

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

for mod in $MOD_LIVEPATCH2 $MOD_LIVEPATCH3; do
	load_lp "$mod"
done

mods=($SYSFS_KLP_DIR/*)
nmods=${#mods[@]}
if [ "$nmods" -ne 3 ]; then
	die "Expecting three modules listed, found $nmods"
fi

load_lp $MOD_REPLACE replace=1

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

loop_until 'mods=($SYSFS_KLP_DIR/*); nmods=${#mods[@]}; [[ "$nmods" -eq 1 ]]' ||
        die "Expecting only one moduled listed, found $nmods"

# These modules were disabled by the atomic replace
for mod in $MOD_LIVEPATCH3 $MOD_LIVEPATCH2 $MOD_LIVEPATCH1; do
	unload_lp "$mod"
done

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

disable_lp $MOD_REPLACE
unload_lp $MOD_REPLACE

grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg

check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko
livepatch: enabling patch '$MOD_LIVEPATCH1'
livepatch: '$MOD_LIVEPATCH1': initializing patching transition
livepatch: '$MOD_LIVEPATCH1': starting patching transition
livepatch: '$MOD_LIVEPATCH1': completing patching transition
livepatch: '$MOD_LIVEPATCH1': patching complete
$MOD_LIVEPATCH1: this has been live patched
% insmod test_modules/$MOD_LIVEPATCH2.ko
livepatch: enabling patch '$MOD_LIVEPATCH2'
livepatch: '$MOD_LIVEPATCH2': initializing patching transition
livepatch: '$MOD_LIVEPATCH2': starting patching transition
livepatch: '$MOD_LIVEPATCH2': completing patching transition
livepatch: '$MOD_LIVEPATCH2': patching complete
% insmod test_modules/$MOD_LIVEPATCH3.ko
livepatch: enabling patch '$MOD_LIVEPATCH3'
livepatch: '$MOD_LIVEPATCH3': initializing patching transition
$MOD_LIVEPATCH3: pre_patch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH3': starting patching transition
livepatch: '$MOD_LIVEPATCH3': completing patching transition
$MOD_LIVEPATCH3: post_patch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH3': patching complete
% insmod test_modules/$MOD_REPLACE.ko replace=1
livepatch: enabling patch '$MOD_REPLACE'
livepatch: '$MOD_REPLACE': initializing patching transition
livepatch: '$MOD_REPLACE': starting patching transition
livepatch: '$MOD_REPLACE': completing patching transition
livepatch: '$MOD_REPLACE': patching complete
$MOD_REPLACE: this has been live patched
% rmmod $MOD_LIVEPATCH3
% rmmod $MOD_LIVEPATCH2
% rmmod $MOD_LIVEPATCH1
$MOD_REPLACE: this has been live patched
% echo 0 > $SYSFS_KLP_DIR/$MOD_REPLACE/enabled
livepatch: '$MOD_REPLACE': initializing unpatching transition
livepatch: '$MOD_REPLACE': starting unpatching transition
livepatch: '$MOD_REPLACE': completing unpatching transition
livepatch: '$MOD_REPLACE': unpatching complete
% rmmod $MOD_REPLACE"


# - load a target module that provides /proc/test_klp_mod_target with
#   original output
# - load a livepatch that patches the target module's show function
# - verify the proc entry returns livepatched output
# - disable and unload the livepatch
# - verify the proc entry returns original output again
# - unload the target module

start_test "module function patching"

load_mod $MOD_TARGET

if [[ "$(cat /proc/$MOD_TARGET)" != "$MOD_TARGET: original output" ]] ; then
	echo -e "FAIL\n\n"
	die "livepatch kselftest(s) failed"
fi

load_lp $MOD_TARGET_PATCH

if [[ "$(cat /proc/$MOD_TARGET)" != "$MOD_TARGET_PATCH: this has been live patched" ]] ; then
	echo -e "FAIL\n\n"
	die "livepatch kselftest(s) failed"
fi

disable_lp $MOD_TARGET_PATCH
unload_lp $MOD_TARGET_PATCH

if [[ "$(cat /proc/$MOD_TARGET)" != "$MOD_TARGET: original output" ]] ; then
	echo -e "FAIL\n\n"
	die "livepatch kselftest(s) failed"
fi

unload_mod $MOD_TARGET

check_result "% insmod test_modules/$MOD_TARGET.ko
$MOD_TARGET: test_klp_mod_target_init
% insmod test_modules/$MOD_TARGET_PATCH.ko
livepatch: enabling patch '$MOD_TARGET_PATCH'
livepatch: '$MOD_TARGET_PATCH': initializing patching transition
livepatch: '$MOD_TARGET_PATCH': starting patching transition
livepatch: '$MOD_TARGET_PATCH': completing patching transition
livepatch: '$MOD_TARGET_PATCH': patching complete
% echo 0 > $SYSFS_KLP_DIR/$MOD_TARGET_PATCH/enabled
livepatch: '$MOD_TARGET_PATCH': initializing unpatching transition
livepatch: '$MOD_TARGET_PATCH': starting unpatching transition
livepatch: '$MOD_TARGET_PATCH': completing unpatching transition
livepatch: '$MOD_TARGET_PATCH': unpatching complete
% rmmod $MOD_TARGET_PATCH
% rmmod $MOD_TARGET
$MOD_TARGET: test_klp_mod_target_exit"


# - load a livepatch that targets a not-yet-loaded module
# - load the target module: klp_module_coming patches it immediately
# - verify the proc entry returns livepatched output
# - disable and unload the livepatch
# - verify the proc entry returns original output again
# - unload the target module

start_test "module function patching (livepatch first)"

load_lp $MOD_TARGET_PATCH
load_mod $MOD_TARGET

if [[ "$(cat /proc/$MOD_TARGET)" != "$MOD_TARGET_PATCH: this has been live patched" ]] ; then
	echo -e "FAIL\n\n"
	die "livepatch kselftest(s) failed"
fi

disable_lp $MOD_TARGET_PATCH
unload_lp $MOD_TARGET_PATCH

if [[ "$(cat /proc/$MOD_TARGET)" != "$MOD_TARGET: original output" ]] ; then
	echo -e "FAIL\n\n"
	die "livepatch kselftest(s) failed"
fi

unload_mod $MOD_TARGET

check_result "% insmod test_modules/$MOD_TARGET_PATCH.ko
livepatch: enabling patch '$MOD_TARGET_PATCH'
livepatch: '$MOD_TARGET_PATCH': initializing patching transition
livepatch: '$MOD_TARGET_PATCH': starting patching transition
livepatch: '$MOD_TARGET_PATCH': completing patching transition
livepatch: '$MOD_TARGET_PATCH': patching complete
% insmod test_modules/$MOD_TARGET.ko
livepatch: applying patch '$MOD_TARGET_PATCH' to loading module '$MOD_TARGET'
$MOD_TARGET: test_klp_mod_target_init
% echo 0 > $SYSFS_KLP_DIR/$MOD_TARGET_PATCH/enabled
livepatch: '$MOD_TARGET_PATCH': initializing unpatching transition
livepatch: '$MOD_TARGET_PATCH': starting unpatching transition
livepatch: '$MOD_TARGET_PATCH': completing unpatching transition
livepatch: '$MOD_TARGET_PATCH': unpatching complete
% rmmod $MOD_TARGET_PATCH
% rmmod $MOD_TARGET
$MOD_TARGET: test_klp_mod_target_exit"


exit 0
