* [RFC] livepatch: Add integration tests for klp-build toolchain
@ 2026-10-03 22:04 Song Liu
2026-10-03 22:11 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Song Liu @ 2026-10-03 22:04 UTC (permalink / raw)
To: live-patching; +Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, Song Liu
Add integration tests for the klp-build toolchain. This includes kernel
side test code and .patch files. The tests cover both livepatch to vmlinux
and kernel modules.
The test target code lives in lib/livepatch/: klp_test_vmlinux.c and
klp_test_vmlinux_aux.c are built into vmlinux (CONFIG_KLP_TEST), while
klp_test_module.c builds as a loadable module (CONFIG_KLP_TEST_MODULE).
klp_test_vmlinux_aux.c carries its own static __helper() so that ThinLTO
.llvm.<hash> suffix handling is exercised.
klp-build rejects patches to anything under lib/. The blanket rule is
there for two reasons: lib-y objects are archived into lib/lib.a and are
only linked into vmlinux when referenced, so a patched member may not end
up in the kernel at all; and several lib/ sources are compiled a second
time into images which can't be livepatched, such as purgatory, the boot
decompressor and the arm64/riscv early position-independent code.
lib/livepatch/ has neither property, it holds ordinary obj-y/obj-m test
code used by nothing else, so exempt it from the check.
Check tools/testing/selftests/livepatch/test_patches/README for
instructions to run these tests.
Signed-off-by: Song Liu <song@kernel.org>
---
This is a slightly changed version of [1]. The major change here to move is
to move the test target code to lib/livepatch.
Sending this again so that we can discuss this in the upcoming live
patching MC at LPC 2026
[1] https://lore.kernel.org/live-patching/20260226005436.379303-9-song@kernel.org/
Signed-off-by: Song Liu <song@kernel.org>
---
MAINTAINERS | 1 +
kernel/livepatch/Kconfig | 20 +++
lib/Makefile | 1 +
lib/livepatch/Makefile | 6 +
lib/livepatch/klp_test_module.c | 111 ++++++++++++++
lib/livepatch/klp_test_module.h | 8 +
lib/livepatch/klp_test_vmlinux.c | 138 ++++++++++++++++++
lib/livepatch/klp_test_vmlinux.h | 16 ++
lib/livepatch/klp_test_vmlinux_aux.c | 59 ++++++++
scripts/livepatch/klp-build | 10 ++
.../selftests/livepatch/test_patches/README | 15 ++
.../test_patches/klp_test_hash_change.patch | 30 ++++
.../test_patches/klp_test_module.patch | 18 +++
.../klp_test_nonstatic_to_static.patch | 40 +++++
.../klp_test_static_to_nonstatic.patch | 39 +++++
.../test_patches/klp_test_vmlinux.patch | 18 +++
16 files changed, 530 insertions(+)
create mode 100644 lib/livepatch/Makefile
create mode 100644 lib/livepatch/klp_test_module.c
create mode 100644 lib/livepatch/klp_test_module.h
create mode 100644 lib/livepatch/klp_test_vmlinux.c
create mode 100644 lib/livepatch/klp_test_vmlinux.h
create mode 100644 lib/livepatch/klp_test_vmlinux_aux.c
create mode 100644 tools/testing/selftests/livepatch/test_patches/README
create mode 100644 tools/testing/selftests/livepatch/test_patches/klp_test_hash_change.patch
create mode 100644 tools/testing/selftests/livepatch/test_patches/klp_test_module.patch
create mode 100644 tools/testing/selftests/livepatch/test_patches/klp_test_nonstatic_to_static.patch
create mode 100644 tools/testing/selftests/livepatch/test_patches/klp_test_static_to_nonstatic.patch
create mode 100644 tools/testing/selftests/livepatch/test_patches/klp_test_vmlinux.patch
diff --git a/MAINTAINERS b/MAINTAINERS
index 7235a92ff879..67d84812f043 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15169,6 +15169,7 @@ F: arch/powerpc/include/asm/livepatch.h
F: include/linux/livepatch*.h
F: kernel/livepatch/
F: kernel/module/livepatch.c
+F: lib/livepatch/
F: samples/livepatch/
F: scripts/livepatch/
F: tools/testing/selftests/livepatch/
diff --git a/kernel/livepatch/Kconfig b/kernel/livepatch/Kconfig
index 4c0a9c18d0b2..48262246444c 100644
--- a/kernel/livepatch/Kconfig
+++ b/kernel/livepatch/Kconfig
@@ -30,3 +30,23 @@ config KLP_BUILD
select OBJTOOL
help
Enable klp-build support
+
+config KLP_TEST
+ bool "Livepatch test code"
+ depends on LIVEPATCH
+ help
+ Dummy kernel code for testing the klp-build livepatch toolchain.
+ Provides built-in vmlinux functions with sysfs interfaces for
+ verifying livepatches. The code lives in lib/livepatch/.
+
+ If unsure, say N.
+
+config KLP_TEST_MODULE
+ tristate "Livepatch test module (klp_test_module)"
+ depends on KLP_TEST && m
+ help
+ Test module for livepatch testing. Dummy kernel module for
+ testing the klp-build toolchain. Provides sysfs interfaces for
+ verifying livepatches.
+
+ If unsure, say N.
diff --git a/lib/Makefile b/lib/Makefile
index dfab958327c5..03543b2fb8ba 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_FIND_BIT_BENCHMARK) += find_bit_benchmark.o
obj-$(CONFIG_REGION_ALLOC_BENCHMARK) += region_alloc_benchmark.o
obj-$(CONFIG_FIND_BIT_BENCHMARK_RUST) += find_bit_benchmark_rust.o
obj-$(CONFIG_TEST_BPF) += test_bpf.o
+obj-$(CONFIG_KLP_TEST) += livepatch/
test_dhry-objs := dhry_1.o dhry_2.o dhry_run.o
obj-$(CONFIG_TEST_DHRY) += test_dhry.o
obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
diff --git a/lib/livepatch/Makefile b/lib/livepatch/Makefile
new file mode 100644
index 000000000000..7b2748c821d5
--- /dev/null
+++ b/lib/livepatch/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-y += klp_test_vmlinux_all.o
+obj-$(CONFIG_KLP_TEST_MODULE) += klp_test_module.o
+
+klp_test_vmlinux_all-y := klp_test_vmlinux.o \
+ klp_test_vmlinux_aux.o
diff --git a/lib/livepatch/klp_test_module.c b/lib/livepatch/klp_test_module.c
new file mode 100644
index 000000000000..25cefbe36a2b
--- /dev/null
+++ b/lib/livepatch/klp_test_module.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * klp_test_module.c - Single-file test module for livepatch/klp-build testing
+ *
+ * Copyright (C) 2026 Meta Platforms, Inc. and affiliates.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/string.h>
+#include "klp_test_module.h"
+#include "klp_test_vmlinux.h"
+
+static int klp_test_module_var1;
+static int klp_test_module_var2;
+
+static noinline ssize_t __klp_test_module_func1(char *buf, int len)
+{
+ ssize_t ret = 0;
+ int i;
+
+ for (i = 0; i < len; i++)
+ klp_test_module_var1 += i;
+
+ if (klp_test_module_var1 > 1000)
+ klp_test_module_var1 = 0;
+
+ ret = sysfs_emit(buf, "klp_test_module_func1 unpatched %d\n",
+ klp_test_module_var1);
+ return ret;
+}
+
+ssize_t klp_test_module_func1(char *buf, int len)
+{
+ return __klp_test_module_func1(buf, len);
+}
+EXPORT_SYMBOL_GPL(klp_test_module_func1);
+
+static noinline ssize_t __klp_test_module_func2(char *buf, int len)
+{
+ ssize_t ret = 0;
+ int i;
+
+ for (i = 0; i < len; i++)
+ klp_test_module_var2 += i * 2;
+
+ if (klp_test_module_var2 > 1000)
+ klp_test_module_var2 = 0;
+
+ ret = sysfs_emit(buf, "klp_test_module_func2 unpatched %d\n",
+ klp_test_module_var2);
+ return ret;
+}
+
+ssize_t klp_test_module_func2(char *buf, int len)
+{
+ return __klp_test_module_func2(buf, len);
+}
+EXPORT_SYMBOL_GPL(klp_test_module_func2);
+
+static ssize_t func1_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return klp_test_module_func1(buf, 5);
+}
+
+static ssize_t func2_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return klp_test_module_func2(buf, 5);
+}
+
+static struct kobj_attribute func1_attr = __ATTR_RO(func1);
+static struct kobj_attribute func2_attr = __ATTR_RO(func2);
+
+static struct attribute *klp_test_module_attrs[] = {
+ &func1_attr.attr,
+ &func2_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group klp_test_module_attr_group = {
+ .attrs = klp_test_module_attrs,
+};
+
+static struct kobject *klp_test_module_kobj;
+
+static int __init klp_test_module_init(void)
+{
+ klp_test_module_kobj = kobject_create_and_add("module",
+ klp_test_kobj);
+ if (!klp_test_module_kobj)
+ return -ENOMEM;
+
+ return sysfs_create_group(klp_test_module_kobj,
+ &klp_test_module_attr_group);
+}
+
+static void __exit klp_test_module_exit(void)
+{
+ sysfs_remove_group(klp_test_module_kobj, &klp_test_module_attr_group);
+ kobject_put(klp_test_module_kobj);
+}
+
+module_init(klp_test_module_init);
+module_exit(klp_test_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Livepatch single-file test module");
diff --git a/lib/livepatch/klp_test_module.h b/lib/livepatch/klp_test_module.h
new file mode 100644
index 000000000000..56a766f4744b
--- /dev/null
+++ b/lib/livepatch/klp_test_module.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _KLP_TEST_MODULE_H
+#define _KLP_TEST_MODULE_H
+
+ssize_t klp_test_module_func1(char *buf, int len);
+ssize_t klp_test_module_func2(char *buf, int len);
+
+#endif /* _KLP_TEST_MODULE_H */
diff --git a/lib/livepatch/klp_test_vmlinux.c b/lib/livepatch/klp_test_vmlinux.c
new file mode 100644
index 000000000000..bd4157ea97c0
--- /dev/null
+++ b/lib/livepatch/klp_test_vmlinux.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * klp_test_vmlinux.c - Dummy built-in code for livepatch/klp-build testing
+ *
+ * Copyright (C) 2026 Meta Platforms, Inc. and affiliates.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include "klp_test_vmlinux.h"
+
+static int klp_test_vmlinux_var1;
+static int klp_test_vmlinux_var2;
+
+static noinline int __helper(int x, int len)
+{
+ int i, sum = x;
+
+ for (i = 0; i < len; i++)
+ sum += i + 5;
+ if (sum > 1000)
+ sum = 0;
+ return sum;
+}
+
+static noinline ssize_t __klp_test_vmlinux_func1(char *buf, int len)
+{
+ ssize_t ret = 0;
+
+ klp_test_vmlinux_var1 = __helper(klp_test_vmlinux_var1, len);
+
+ ret = sysfs_emit(buf, "klp_test_vmlinux_func1 unpatched %d\n",
+ klp_test_vmlinux_var1);
+ return ret;
+}
+
+ssize_t klp_test_vmlinux_func1(char *buf, int len)
+{
+ return __klp_test_vmlinux_func1(buf, len);
+}
+EXPORT_SYMBOL_GPL(klp_test_vmlinux_func1);
+
+static noinline ssize_t __klp_test_vmlinux_func2(char *buf, int len)
+{
+ ssize_t ret = 0;
+ int i;
+
+ for (i = 0; i < len; i++)
+ klp_test_vmlinux_var2 += i * 2;
+
+ if (klp_test_vmlinux_var2 > 1000)
+ klp_test_vmlinux_var2 = 0;
+
+ ret = sysfs_emit(buf, "klp_test_vmlinux_func2 unpatched %d\n",
+ klp_test_vmlinux_var2);
+ return ret;
+}
+
+ssize_t klp_test_vmlinux_func2(char *buf, int len)
+{
+ return __klp_test_vmlinux_func2(buf, len);
+}
+EXPORT_SYMBOL_GPL(klp_test_vmlinux_func2);
+
+static ssize_t vmlinux_func1_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return klp_test_vmlinux_func1(buf, 5);
+}
+
+static ssize_t vmlinux_func2_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return klp_test_vmlinux_func2(buf, 5);
+}
+
+static struct kobj_attribute vmlinux_func1_attr = __ATTR_RO(vmlinux_func1);
+static struct kobj_attribute vmlinux_func2_attr = __ATTR_RO(vmlinux_func2);
+
+static struct attribute *klp_test_attrs[] = {
+ &vmlinux_func1_attr.attr,
+ &vmlinux_func2_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group klp_test_attr_group = {
+ .attrs = klp_test_attrs,
+};
+
+static struct kobject *klp_test_vmlinux_kobj;
+struct kobject *klp_test_kobj;
+EXPORT_SYMBOL_GPL(klp_test_kobj);
+
+static int __init klp_test_vmlinux_init(void)
+{
+ int ret;
+
+ klp_test_kobj = kobject_create_and_add("klp_test", kernel_kobj);
+ if (!klp_test_kobj)
+ return -ENOMEM;
+
+ klp_test_vmlinux_kobj = kobject_create_and_add("vmlinux", klp_test_kobj);
+ if (!klp_test_vmlinux_kobj) {
+ kobject_put(klp_test_kobj);
+ return -ENOMEM;
+ }
+
+ ret = sysfs_create_group(klp_test_vmlinux_kobj, &klp_test_attr_group);
+ if (ret)
+ goto err_group;
+
+ ret = klp_test_vmlinux_aux_init(klp_test_vmlinux_kobj);
+ if (ret)
+ goto err_aux;
+
+ return 0;
+
+err_aux:
+ sysfs_remove_group(klp_test_vmlinux_kobj, &klp_test_attr_group);
+err_group:
+ kobject_put(klp_test_vmlinux_kobj);
+ kobject_put(klp_test_kobj);
+ return ret;
+}
+
+static void __exit klp_test_vmlinux_exit(void)
+{
+ klp_test_vmlinux_aux_exit(klp_test_vmlinux_kobj);
+ sysfs_remove_group(klp_test_vmlinux_kobj, &klp_test_attr_group);
+ kobject_put(klp_test_vmlinux_kobj);
+ kobject_put(klp_test_kobj);
+}
+
+late_initcall(klp_test_vmlinux_init);
diff --git a/lib/livepatch/klp_test_vmlinux.h b/lib/livepatch/klp_test_vmlinux.h
new file mode 100644
index 000000000000..56d9f7b6d350
--- /dev/null
+++ b/lib/livepatch/klp_test_vmlinux.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _KLP_TEST_VMLINUX_H
+#define _KLP_TEST_VMLINUX_H
+
+#include <linux/kobject.h>
+
+extern struct kobject *klp_test_kobj;
+
+ssize_t klp_test_vmlinux_func1(char *buf, int len);
+ssize_t klp_test_vmlinux_func2(char *buf, int len);
+ssize_t klp_test_vmlinux_func3(char *buf, int len);
+
+int klp_test_vmlinux_aux_init(struct kobject *parent);
+void klp_test_vmlinux_aux_exit(struct kobject *parent);
+
+#endif /* _KLP_TEST_VMLINUX_H */
diff --git a/lib/livepatch/klp_test_vmlinux_aux.c b/lib/livepatch/klp_test_vmlinux_aux.c
new file mode 100644
index 000000000000..1d76b0308a11
--- /dev/null
+++ b/lib/livepatch/klp_test_vmlinux_aux.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * klp_test_vmlinux_aux.c - Auxiliary built-in code for livepatch/klp-build
+ * testing. This file has its own static __helper()
+ * to test ThinLTO .llvm.<hash> suffix handling.
+ *
+ * Copyright (C) 2026 Meta Platforms, Inc. and affiliates.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/string.h>
+#include "klp_test_vmlinux.h"
+
+static int klp_test_vmlinux_var3;
+
+static noinline int __helper(int x, int len)
+{
+ int i, sum = x;
+
+ for (i = 0; i < len; i++)
+ sum += i + 10;
+ if (sum > 1000)
+ sum = 0;
+ return sum;
+}
+
+static noinline ssize_t __klp_test_vmlinux_func3(char *buf, int len)
+{
+ klp_test_vmlinux_var3 = __helper(klp_test_vmlinux_var3, len);
+
+ return sysfs_emit(buf, "klp_test_vmlinux_func3 unpatched %d\n",
+ klp_test_vmlinux_var3);
+}
+
+ssize_t klp_test_vmlinux_func3(char *buf, int len)
+{
+ return __klp_test_vmlinux_func3(buf, len);
+}
+EXPORT_SYMBOL_GPL(klp_test_vmlinux_func3);
+
+static ssize_t vmlinux_func3_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return klp_test_vmlinux_func3(buf, 5);
+}
+
+static struct kobj_attribute vmlinux_func3_attr = __ATTR_RO(vmlinux_func3);
+
+int klp_test_vmlinux_aux_init(struct kobject *parent)
+{
+ return sysfs_create_file(parent, &vmlinux_func3_attr.attr);
+}
+
+void klp_test_vmlinux_aux_exit(struct kobject *parent)
+{
+ sysfs_remove_file(parent, &vmlinux_func3_attr.attr);
+}
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index b52a8489d9f6..cdb3a9489329 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -365,6 +365,16 @@ check_unsupported_patches() {
for file in "${files[@]}"; do
case "$file" in
+ # lib/ is unsupported in general: lib-y objects
+ # are archived into lib/lib.a and only linked
+ # into vmlinux when referenced, and several
+ # lib/ sources are compiled a second time into
+ # images which can't be livepatched (purgatory,
+ # the boot decompressor, early PI code).
+ # lib/livepatch/ has neither property, it's
+ # ordinary obj-y/obj-m klp-build test code.
+ lib/livepatch/*)
+ ;;
lib/*|*/vdso/*|*/realmode/rm/*|*.S)
die "${patch}: unsupported patch to $file"
;;
diff --git a/tools/testing/selftests/livepatch/test_patches/README b/tools/testing/selftests/livepatch/test_patches/README
new file mode 100644
index 000000000000..3c880f28d852
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_patches/README
@@ -0,0 +1,15 @@
+This is folder contains patches to test the klp-build toolchain.
+
+To run the test:
+
+1. Enable CONFIG_KLP_TEST and CONFIG_KLP_TEST_MODULE, and build the kernel.
+
+2. Build these patches with:
+
+ ./scripts/livepatch/klp-build tools/testing/selftests/livepatch/test_patches/*.patch
+
+3. Verify the correctness with:
+
+ modprobe klp_test_module
+ insmod livepatch-patch.ko
+ grep -q unpatched /sys/kernel/klp_test/*/* && echo FAIL || echo PASS
diff --git a/tools/testing/selftests/livepatch/test_patches/klp_test_hash_change.patch b/tools/testing/selftests/livepatch/test_patches/klp_test_hash_change.patch
new file mode 100644
index 000000000000..73420377f0e7
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_patches/klp_test_hash_change.patch
@@ -0,0 +1,30 @@
+Test ThinLTO .llvm.<hash> suffix handling.
+
+Modify a static __helper() function whose body change causes its
+.llvm.<hash> suffix to change under ThinLTO. Both klp_test_vmlinux.c
+and klp_test_vmlinux_aux.c define static __helper() with different
+bodies, so ThinLTO promotes both to globals with different hashes.
+This patch changes the __helper() in the aux file, which changes its
+hash, and klp-build must correctly match the old and new symbols.
+
+diff --git i/lib/livepatch/klp_test_vmlinux_aux.c w/lib/livepatch/klp_test_vmlinux_aux.c
+--- i/lib/livepatch/klp_test_vmlinux_aux.c
++++ w/lib/livepatch/klp_test_vmlinux_aux.c
+@@ -20,7 +20,7 @@
+ int i, sum = x;
+
+ for (i = 0; i < len; i++)
+- sum += i + 10;
++ sum += i * 2 + 10;
+ if (sum > 1000)
+ sum = 0;
+ return sum;
+@@ -30,7 +30,7 @@
+ {
+ klp_test_vmlinux_var3 = __helper(klp_test_vmlinux_var3, len);
+
+- return sysfs_emit(buf, "klp_test_vmlinux_func3 unpatched %d\n",
++ return sysfs_emit(buf, "klp_test_vmlinux_func3 hash_patched %d\n",
+ klp_test_vmlinux_var3);
+ }
+
diff --git a/tools/testing/selftests/livepatch/test_patches/klp_test_module.patch b/tools/testing/selftests/livepatch/test_patches/klp_test_module.patch
new file mode 100644
index 000000000000..fae1400b063a
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_patches/klp_test_module.patch
@@ -0,0 +1,18 @@
+Test basic module patching.
+
+Patch a loadable module function to verify that klp-build can generate
+a livepatch for module code. Changes __klp_test_module_func1() output
+from "unpatched" to "patched".
+
+diff --git i/lib/livepatch/klp_test_module.c w/lib/livepatch/klp_test_module.c
+--- i/lib/livepatch/klp_test_module.c
++++ w/lib/livepatch/klp_test_module.c
+@@ -27,7 +27,7 @@
+ if (klp_test_module_var1 > 1000)
+ klp_test_module_var1 = 0;
+
+- ret = sysfs_emit(buf, "klp_test_module_func1 unpatched %d\n",
++ ret = sysfs_emit(buf, "klp_test_module_func1 patched %d\n",
+ klp_test_module_var1);
+ return ret;
+ }
diff --git a/tools/testing/selftests/livepatch/test_patches/klp_test_nonstatic_to_static.patch b/tools/testing/selftests/livepatch/test_patches/klp_test_nonstatic_to_static.patch
new file mode 100644
index 000000000000..112c93c50e6e
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_patches/klp_test_nonstatic_to_static.patch
@@ -0,0 +1,40 @@
+Test nonstatic-to-static symbol change.
+
+Change klp_test_module_func2() from nonstatic (global) to static and
+remove its EXPORT_SYMBOL_GPL. Also remove its declaration from the
+header file. This tests klp-build's ability to handle symbol visibility
+changes where a function that was originally global becomes static in
+the patched kernel.
+
+diff --git i/lib/livepatch/klp_test_module.c w/lib/livepatch/klp_test_module.c
+--- i/lib/livepatch/klp_test_module.c
++++ w/lib/livepatch/klp_test_module.c
+@@ -49,16 +49,15 @@
+ if (klp_test_module_var2 > 1000)
+ klp_test_module_var2 = 0;
+
+- ret = sysfs_emit(buf, "klp_test_module_func2 unpatched %d\n",
++ ret = sysfs_emit(buf, "klp_test_module_func2 patched_nts %d\n",
+ klp_test_module_var2);
+ return ret;
+ }
+
+-ssize_t klp_test_module_func2(char *buf, int len)
++static noinline ssize_t klp_test_module_func2(char *buf, int len)
+ {
+ return __klp_test_module_func2(buf, len);
+ }
+-EXPORT_SYMBOL_GPL(klp_test_module_func2);
+
+ static ssize_t func1_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+diff --git i/lib/livepatch/klp_test_module.h w/lib/livepatch/klp_test_module.h
+--- i/lib/livepatch/klp_test_module.h
++++ w/lib/livepatch/klp_test_module.h
+@@ -3,6 +3,5 @@
+ #define _KLP_TEST_MODULE_H
+
+ ssize_t klp_test_module_func1(char *buf, int len);
+-ssize_t klp_test_module_func2(char *buf, int len);
+
+ #endif /* _KLP_TEST_MODULE_H */
diff --git a/tools/testing/selftests/livepatch/test_patches/klp_test_static_to_nonstatic.patch b/tools/testing/selftests/livepatch/test_patches/klp_test_static_to_nonstatic.patch
new file mode 100644
index 000000000000..357c9c98189d
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_patches/klp_test_static_to_nonstatic.patch
@@ -0,0 +1,39 @@
+Test static-to-nonstatic symbol change.
+
+Change __klp_test_vmlinux_func2() from static to nonstatic (global).
+This tests klp-build's ability to handle symbol visibility changes
+where a function that was originally static becomes globally visible
+in the patched kernel.
+
+diff --git i/lib/livepatch/klp_test_vmlinux.c w/lib/livepatch/klp_test_vmlinux.c
+--- i/lib/livepatch/klp_test_vmlinux.c
++++ w/lib/livepatch/klp_test_vmlinux.c
+@@ -44,7 +44,7 @@
+ }
+ EXPORT_SYMBOL_GPL(klp_test_vmlinux_func1);
+
+-static noinline ssize_t __klp_test_vmlinux_func2(char *buf, int len)
++noinline ssize_t __klp_test_vmlinux_func2(char *buf, int len)
+ {
+ ssize_t ret = 0;
+ int i;
+@@ -55,7 +55,7 @@
+ if (klp_test_vmlinux_var2 > 1000)
+ klp_test_vmlinux_var2 = 0;
+
+- ret = sysfs_emit(buf, "klp_test_vmlinux_func2 unpatched %d\n",
++ ret = sysfs_emit(buf, "klp_test_vmlinux_func2 patched_stn %d\n",
+ klp_test_vmlinux_var2);
+ return ret;
+ }
+diff --git i/lib/livepatch/klp_test_vmlinux.h w/lib/livepatch/klp_test_vmlinux.h
+--- i/lib/livepatch/klp_test_vmlinux.h
++++ w/lib/livepatch/klp_test_vmlinux.h
+@@ -9,6 +9,7 @@
+ ssize_t klp_test_vmlinux_func1(char *buf, int len);
+ ssize_t klp_test_vmlinux_func2(char *buf, int len);
+ ssize_t klp_test_vmlinux_func3(char *buf, int len);
++ssize_t __klp_test_vmlinux_func2(char *buf, int len);
+
+ int klp_test_vmlinux_aux_init(struct kobject *parent);
+ void klp_test_vmlinux_aux_exit(struct kobject *parent);
diff --git a/tools/testing/selftests/livepatch/test_patches/klp_test_vmlinux.patch b/tools/testing/selftests/livepatch/test_patches/klp_test_vmlinux.patch
new file mode 100644
index 000000000000..ded12e0dade1
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_patches/klp_test_vmlinux.patch
@@ -0,0 +1,18 @@
+Test basic vmlinux patching.
+
+Patch a built-in vmlinux function to verify that klp-build can generate
+a livepatch for vmlinux code. Changes __klp_test_vmlinux_func1() output
+from "unpatched" to "patched".
+
+diff --git i/lib/livepatch/klp_test_vmlinux.c w/lib/livepatch/klp_test_vmlinux.c
+--- i/lib/livepatch/klp_test_vmlinux.c
++++ w/lib/livepatch/klp_test_vmlinux.c
+@@ -33,7 +33,7 @@
+
+ klp_test_vmlinux_var1 = __helper(klp_test_vmlinux_var1, len);
+
+- ret = sysfs_emit(buf, "klp_test_vmlinux_func1 unpatched %d\n",
++ ret = sysfs_emit(buf, "klp_test_vmlinux_func1 patched %d\n",
+ klp_test_vmlinux_var1);
+ return ret;
+ }
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [RFC] livepatch: Add integration tests for klp-build toolchain
2026-10-03 22:04 [RFC] livepatch: Add integration tests for klp-build toolchain Song Liu
@ 2026-10-03 22:11 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-03 22:11 UTC (permalink / raw)
To: live-patching, Song Liu; +Cc: jpoimboe, mbenes, jikos, joe.lawrence, pmladek
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] livepatch: use-after-free in klp_test_module_init() via dangling klp_test_kobj pointer
- [Medium] livepatch: memory leak of klp_test_module_kobj on sysfs_create_group() failure
--
commit 440c68ecb776cce99d846849a24bfeacf1e70de3
Author: Song Liu <song@kernel.org>
Subject: livepatch: Add integration tests for klp-build toolchain
This commit adds integration tests for the klp-build toolchain, including
kernel side dummy code built into vmlinux and as a loadable module. It
provides sysfs interfaces to verify livepatches and exercises ThinLTO
suffix handling.
Link: https://lore.kernel.org/live-patching/20260226005436.379303-9-song@kernel.org/
[Severity: Medium]
In lib/livepatch/klp_test_module.c, inside klp_test_module_init() at line
95, the code returns the result of sysfs_create_group() directly. If this
function fails, does it bypass the cleanup for the recently created
klp_test_module_kobj? Should there be a call to
kobject_put(klp_test_module_kobj) on this error path to prevent a memory
leak?
[Severity: High]
In lib/livepatch/klp_test_vmlinux.c, inside klp_test_vmlinux_init() at
lines 105 and 126, there are error paths where klp_test_kobj is freed via
kobject_put(klp_test_kobj). However, the globally exported pointer
klp_test_kobj is not reset to NULL.
Since klp_test_vmlinux_init() is a late_initcall, an error here does not
halt the system boot. Later, when the klp_test_module is loaded,
klp_test_module_init() in lib/livepatch/klp_test_module.c at line 90
attempts to pass this dangling klp_test_kobj pointer to
kobject_create_and_add() as the parent.
Could this sequence trigger a use-after-free when the kobject core attempts
to access the freed parent's refcount? Should klp_test_kobj be explicitly
set to NULL on the error paths in klp_test_vmlinux_init(), and should
klp_test_module_init() check if klp_test_kobj is valid before using it?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261003220433.1539629-1-song@kernel.org?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-03 22:11 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-03 22:04 [RFC] livepatch: Add integration tests for klp-build toolchain Song Liu
2026-10-03 22:11 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox