Livepatches with different replace_sets must not share the same state ID. If a second livepatch attempts to reuse a state ID already registered by a livepatch with a different replace_set, the loading will fail.
Add a test case to verify this behavior. The test result is as follows: $ ./test-state.sh TEST: system state modification ... ok TEST: taking over system state modification ... ok TEST: compatible cumulative livepatches ... ok TEST: incompatible cumulative livepatches ... ok TEST: livepatches state compatibility ... ok <<<< This case Signed-off-by: Yafang Shao <[email protected]> --- .../testing/selftests/livepatch/test-state.sh | 34 ++++ .../selftests/livepatch/test_modules/Makefile | 1 + .../livepatch/test_modules/test_klp_state.c | 2 +- .../livepatch/test_modules/test_klp_state2.c | 2 +- .../livepatch/test_modules/test_klp_state4.c | 163 ++++++++++++++++++ 5 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_state4.c diff --git a/tools/testing/selftests/livepatch/test-state.sh b/tools/testing/selftests/livepatch/test-state.sh index 04b66380f8a0..934a2aa655bb 100755 --- a/tools/testing/selftests/livepatch/test-state.sh +++ b/tools/testing/selftests/livepatch/test-state.sh @@ -7,6 +7,7 @@ MOD_LIVEPATCH=test_klp_state MOD_LIVEPATCH2=test_klp_state2 MOD_LIVEPATCH3=test_klp_state3 +MOD_LIVEPATCH4=test_klp_state4 setup_config @@ -173,4 +174,37 @@ $MOD_LIVEPATCH2: free_loglevel_state: freeing space for the stored console_logle livepatch: '$MOD_LIVEPATCH2': unpatching complete % rmmod $MOD_LIVEPATCH2" +# livepatches with different replace_set sharing the same state ID + +start_test "livepatches state compatibility " + +load_lp $MOD_LIVEPATCH +load_failing_mod $MOD_LIVEPATCH4 +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% insmod test_modules/$MOD_LIVEPATCH.ko +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +$MOD_LIVEPATCH: pre_patch_callback: vmlinux +$MOD_LIVEPATCH: allocate_loglevel_state: allocating space to store console_loglevel +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +$MOD_LIVEPATCH: post_patch_callback: vmlinux +$MOD_LIVEPATCH: fix_console_loglevel: fixing console_loglevel +livepatch: '$MOD_LIVEPATCH': patching complete +% insmod test_modules/$MOD_LIVEPATCH4.ko +livepatch: Livepatch patch ($MOD_LIVEPATCH4) is not compatible with the already installed livepatches. +insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH4.ko: Invalid parameters +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux +$MOD_LIVEPATCH: restore_console_loglevel: restoring console_loglevel +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +$MOD_LIVEPATCH: post_unpatch_callback: vmlinux +$MOD_LIVEPATCH: free_loglevel_state: freeing space for the stored console_loglevel +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + exit 0 diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile index a13d398585dc..bdc5ae37311e 100644 --- a/tools/testing/selftests/livepatch/test_modules/Makefile +++ b/tools/testing/selftests/livepatch/test_modules/Makefile @@ -14,6 +14,7 @@ obj-m += test_klp_atomic_replace.o \ test_klp_state.o \ test_klp_state2.o \ test_klp_state3.o \ + test_klp_state4.o \ test_klp_syscall.o # Ensure that KDIR exists, otherwise skip the compilation diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c index 8c8829c3ec43..6ef3059483de 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c @@ -39,7 +39,7 @@ static int allocate_loglevel_state(void) if (!loglevel_state) return -EINVAL; - loglevel_state->data = kzalloc(sizeof(console_loglevel), GFP_KERNEL); + loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL); if (!loglevel_state->data) return -ENOMEM; diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c index 8a79d7dcce33..908d79bc7540 100644 --- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c @@ -46,7 +46,7 @@ static int allocate_loglevel_state(void) if (!loglevel_state) return -EINVAL; - loglevel_state->data = kzalloc(sizeof(console_loglevel), GFP_KERNEL); + loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL); if (!loglevel_state->data) return -ENOMEM; diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c new file mode 100644 index 000000000000..010d1e13f6ec --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This test is identical to test_klp_state4.c, except that replace_set is + * set to 4 instead of 0. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/printk.h> +#include <linux/livepatch.h> + +#define CONSOLE_LOGLEVEL_STATE 1 +#define CONSOLE_LOGLEVEL_STATE_VERSION 4 + +static const char *const module_state[] = { + [MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state", + [MODULE_STATE_COMING] = "[MODULE_STATE_COMING] Full formed, running module_init", + [MODULE_STATE_GOING] = "[MODULE_STATE_GOING] Going away", + [MODULE_STATE_UNFORMED] = "[MODULE_STATE_UNFORMED] Still setting it up", +}; + +static void callback_info(const char *callback, struct klp_object *obj) +{ + if (obj->mod) + pr_info("%s: %s -> %s\n", callback, obj->mod->name, + module_state[obj->mod->state]); + else + pr_info("%s: vmlinux\n", callback); +} + +static struct klp_patch patch; + +static int allocate_loglevel_state(void) +{ + struct klp_state *loglevel_state; + + loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); + if (!loglevel_state) + return -EINVAL; + + loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL); + if (!loglevel_state->data) + return -ENOMEM; + + pr_info("%s: allocating space to store console_loglevel\n", + __func__); + return 0; +} + +static void fix_console_loglevel(void) +{ + struct klp_state *loglevel_state; + + loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); + if (!loglevel_state) + return; + + pr_info("%s: fixing console_loglevel\n", __func__); + *(int *)loglevel_state->data = console_loglevel; + console_loglevel = CONSOLE_LOGLEVEL_MOTORMOUTH; +} + +static void restore_console_loglevel(void) +{ + struct klp_state *loglevel_state; + + loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); + if (!loglevel_state) + return; + + pr_info("%s: restoring console_loglevel\n", __func__); + console_loglevel = *(int *)loglevel_state->data; +} + +static void free_loglevel_state(void) +{ + struct klp_state *loglevel_state; + + loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE); + if (!loglevel_state) + return; + + pr_info("%s: freeing space for the stored console_loglevel\n", + __func__); + kfree(loglevel_state->data); +} + +/* Executed on object patching (ie, patch enablement) */ +static int pre_patch_callback(struct klp_object *obj) +{ + callback_info(__func__, obj); + return allocate_loglevel_state(); +} + +/* Executed on object unpatching (ie, patch disablement) */ +static void post_patch_callback(struct klp_object *obj) +{ + callback_info(__func__, obj); + fix_console_loglevel(); +} + +/* Executed on object unpatching (ie, patch disablement) */ +static void pre_unpatch_callback(struct klp_object *obj) +{ + callback_info(__func__, obj); + restore_console_loglevel(); +} + +/* Executed on object unpatching (ie, patch disablement) */ +static void post_unpatch_callback(struct klp_object *obj) +{ + callback_info(__func__, obj); + free_loglevel_state(); +} + +static struct klp_func no_funcs[] = { + {} +}; + +static struct klp_object objs[] = { + { + .name = NULL, /* vmlinux */ + .funcs = no_funcs, + .callbacks = { + .pre_patch = pre_patch_callback, + .post_patch = post_patch_callback, + .pre_unpatch = pre_unpatch_callback, + .post_unpatch = post_unpatch_callback, + }, + }, { } +}; + +static struct klp_state states[] = { + { + .id = CONSOLE_LOGLEVEL_STATE, + .version = CONSOLE_LOGLEVEL_STATE_VERSION, + }, { } +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, + .states = states, + .replace_set = 4, +}; + +static int test_klp_callbacks_demo_init(void) +{ + return klp_enable_patch(&patch); +} + +static void test_klp_callbacks_demo_exit(void) +{ +} + +module_init(test_klp_callbacks_demo_init); +module_exit(test_klp_callbacks_demo_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_DESCRIPTION("Livepatch test: system state compatibility verification"); -- 2.52.0
