From: Harry Hsu <[email protected]> klp_init_object_loaded() now rejects an object whose klp_funcs resolve to the same address, because aliased symbols would push two klp_funcs of one livepatch onto a single ops->func_stack and leave the redirection ambiguous.
Add a target module providing test_klp_alias_show() together with its __alias() sibling, and a livepatch naming both of them. Two test cases cover both callers of klp_init_object_loaded(): the klp_enable_patch() path, where the target module is loaded before the livepatch, and the klp_module_coming() path, where the livepatch is loaded first and the module loader has to refuse the target module. Suggested-by: Song Liu <[email protected]> Signed-off-by: Harry Hsu <[email protected]> --- tools/testing/selftests/livepatch/Makefile | 3 +- .../testing/selftests/livepatch/test-alias.sh | 81 +++++++++++++++++++ .../selftests/livepatch/test_modules/Makefile | 4 +- .../test_modules/test_klp_alias_patch.c | 62 ++++++++++++++ .../test_modules/test_klp_alias_target.c | 48 +++++++++++ 5 files changed, 196 insertions(+), 2 deletions(-) create mode 100755 tools/testing/selftests/livepatch/test-alias.sh create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_alias_patch.c create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_alias_target.c diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/selftests/livepatch/Makefile index a080eb54a215..ddbeff4cb53d 100644 --- a/tools/testing/selftests/livepatch/Makefile +++ b/tools/testing/selftests/livepatch/Makefile @@ -11,7 +11,8 @@ TEST_PROGS := \ test-ftrace.sh \ test-sysfs.sh \ test-syscall.sh \ - test-kprobe.sh + test-kprobe.sh \ + test-alias.sh TEST_FILES := settings diff --git a/tools/testing/selftests/livepatch/test-alias.sh b/tools/testing/selftests/livepatch/test-alias.sh new file mode 100755 index 000000000000..4ae701de0dbf --- /dev/null +++ b/tools/testing/selftests/livepatch/test-alias.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2026 Harry Hsu <[email protected]> + +. $(dirname $0)/functions.sh + +MOD_TARGET=test_klp_alias_target +MOD_LIVEPATCH=test_klp_alias_patch + +setup_config + + +# $MOD_TARGET provides two symbols that share a single address. A +# livepatch naming both of them would push two klp_funcs of the same +# patch onto one ops->func_stack, leaving the redirection ambiguous, so +# klp_init_object_loaded() has to reject the object. +# +# - load the target module and verify it produces the original output +# - verify that a livepatch naming both aliases fails to load +# - verify that the target module has been left unpatched + +start_test "livepatch of two aliased symbols in one object" + +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_failing_mod $MOD_LIVEPATCH + +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: ${MOD_TARGET}_init +% insmod test_modules/$MOD_LIVEPATCH.ko +livepatch: 'test_klp_alias_show' and 'test_klp_alias_show_alias' resolve to the same address, aliased symbols are not supported +insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH.ko: Invalid parameters +% rmmod $MOD_TARGET +$MOD_TARGET: ${MOD_TARGET}_exit" + + +# The same object is initialized from klp_module_coming() when the +# livepatch is loaded while the target module is still absent. There +# the error has to be propagated to the module loader instead. +# +# - load the livepatch, it is accepted because the object is not loaded +# - verify that loading the target module is refused afterwards + +start_test "aliased symbols in a module coming after the livepatch" + +load_lp $MOD_LIVEPATCH +load_failing_mod $MOD_TARGET +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 +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% insmod test_modules/$MOD_TARGET.ko +livepatch: 'test_klp_alias_show' and 'test_klp_alias_show_alias' resolve to the same address, aliased symbols are not supported +livepatch: failed to initialize patch '$MOD_LIVEPATCH' for module '$MOD_TARGET' (-22) +livepatch: patch '$MOD_LIVEPATCH' failed for module '$MOD_TARGET', refusing to load module '$MOD_TARGET' +insmod: ERROR: could not insert module test_modules/$MOD_TARGET.ko: Invalid parameters +% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +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..532403e2b5ff 100644 --- a/tools/testing/selftests/livepatch/test_modules/Makefile +++ b/tools/testing/selftests/livepatch/test_modules/Makefile @@ -1,7 +1,9 @@ TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST))))) KDIR ?= /lib/modules/$(shell uname -r)/build -obj-m += test_klp_atomic_replace.o \ +obj-m += test_klp_alias_patch.o \ + test_klp_alias_target.o \ + test_klp_atomic_replace.o \ test_klp_callbacks_busy.o \ test_klp_callbacks_demo.o \ test_klp_callbacks_demo2.o \ diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_alias_patch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_alias_patch.c new file mode 100644 index 000000000000..1b50088bc92d --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_alias_patch.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 Harry Hsu <[email protected]> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/livepatch.h> +#include <linux/seq_file.h> + +static int livepatch_alias_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%s: %s\n", THIS_MODULE->name, + "this has been live patched"); + return 0; +} + +/* + * Both names resolve to one address, so they end up on a single + * ops->func_stack and the redirection would be ambiguous. Loading this + * livepatch is expected to fail. + */ +static struct klp_func funcs[] = { + { + .old_name = "test_klp_alias_show", + .new_func = livepatch_alias_show, + }, + { + .old_name = "test_klp_alias_show_alias", + .new_func = livepatch_alias_show, + }, + {}, +}; + +static struct klp_object objs[] = { + { + .name = "test_klp_alias_target", + .funcs = funcs, + }, + {}, +}; + +static struct klp_patch patch = { + .mod = THIS_MODULE, + .objs = objs, +}; + +static int test_klp_alias_patch_init(void) +{ + return klp_enable_patch(&patch); +} + +static void test_klp_alias_patch_exit(void) +{ +} + +module_init(test_klp_alias_patch_init); +module_exit(test_klp_alias_patch_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_AUTHOR("Harry Hsu <[email protected]>"); +MODULE_DESCRIPTION("Livepatch test: patch two aliased symbols of one object"); diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_alias_target.c b/tools/testing/selftests/livepatch/test_modules/test_klp_alias_target.c new file mode 100644 index 000000000000..b0f5fc35adf8 --- /dev/null +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_alias_target.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 Harry Hsu <[email protected]> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> + +static struct proc_dir_entry *pde; + +static noinline int test_klp_alias_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%s: %s\n", THIS_MODULE->name, "original output"); + return 0; +} + +/* + * Alias the function above so that both names resolve to one address, the + * way __do_sys_fork(), __ia32_sys_fork() and __x64_sys_fork() do in vmlinux. + * Nothing calls the alias, it only has to show up in the module's symbol + * table for the livepatch to name it. + */ +static int test_klp_alias_show_alias(struct seq_file *m, void *v) + __used __alias(test_klp_alias_show); + +static int test_klp_alias_target_init(void) +{ + pr_info("%s\n", __func__); + pde = proc_create_single("test_klp_alias_target", 0, NULL, + test_klp_alias_show); + if (!pde) + return -ENOMEM; + return 0; +} + +static void test_klp_alias_target_exit(void) +{ + pr_info("%s\n", __func__); + proc_remove(pde); +} + +module_init(test_klp_alias_target_init); +module_exit(test_klp_alias_target_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harry Hsu <[email protected]>"); +MODULE_DESCRIPTION("Livepatch test: target module with two aliased symbols"); -- 2.55.0

