From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 12:03:36
This is v4 of the patch failing object initialization when it patches
the same underlying function twice through two aliased symbols
(functions sharing one address) in the same object.
Changes against [v3]:
+ Add selftest for livepatching aliased symbols [Song, Harry]
+ Only partially revert relocations when failing inside
klp_apply_object_relocs() [Sashiko]
+ Cleanup changes made by klp_init_object_loaded() for failing
patch on another failures in klp_module_coming() [Sashiko]
Changes against [v2]:
+ Clarify comments and the commit message [Miroslav]
+ Cleanup when klp_init_object_loaded() fails [Sashiko]
[v2] https://lore.kernel.org/r/20260823060734.58443-1-x90613@gmail.com
[v3] https://lore.kernel.org/r/20260830173343.52759-1-x90613@gmail.com
Harry Hsu (2):
livepatch: Fail object initialization on duplicate patched function
selftests/livepatch: Test rejection of aliased symbols in one object
Petr Mladek (3):
livepatch: Move code for updating livepatch object relocations
livepatch: Clear relocations when klp_apply_object_relocs() fails
livepatch: Clean up klp_init_object_loaded() when fails
kernel/livepatch/core.c | 137 ++++++++++++------
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 ++++++
6 files changed, 285 insertions(+), 50 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
--
2.55.0
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 12:03:50
From: Harry Hsu <redacted>
Several symbols can share one address:
ffffffff8ed7fef0 t __do_sys_fork
ffffffff8ed7fef0 T __ia32_sys_fork
ffffffff8ed7fef0 T __x64_sys_fork
klp_find_ops() looks the ops up by func->old_func, i.e. by address, so
two klp_funcs of the same livepatch naming two of these symbols resolve
to the same klp_ops and are both pushed onto one ops->func_stack.
This breaks the assumption that a single livepatch contributes at most
one entry to any func_stack. klp_ftrace_handler() picks the entry at
the top of the stack, but when both entries belong to the same livepatch
there is nothing that says which of them should be used in the PATCHED
state, and the UNPATCHED state has to end up at the original function
either way. klp_check_stack_func() cannot tell them apart either: it
asks whether the preceding entry is the original function or another
livepatch's replacement, and an aliased sibling is neither.
Patching two aliases of one function from a single livepatch was never
meaningful, so fail object initialization in klp_init_object_loaded()
rather than leave the redirection undefined.
Fixes: 3c33f5b99d68 ("livepatch: support for repatching a function")
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Harry Hsu <redacted>
---
kernel/livepatch/core.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
@@ -885,6 +885,21 @@ static int klp_init_object_loaded(struct klp_patch *patch,if(ret)returnret;+/*+*Aliasedsymbolsshareoneaddress,sotheywouldresolveto+*thesameklp_opsandstackuponasingleops->func_stack,+*leavingtheredirectionambiguous.+*/+klp_for_each_func(obj,prev_func){+if(prev_func==func)+break;+if(prev_func->old_func==func->old_func){+pr_err("'%s' and '%s' resolve to the same address, aliased symbols are not supported\n",+prev_func->old_name,func->old_name);+return-EINVAL;+}+}+ret=kallsyms_lookup_size_offset((unsignedlong)func->old_func,&func->old_size,NULL);if(!ret){
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 12:04:03
From: Harry Hsu <redacted>
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 <song@kernel.org>
Signed-off-by: Harry Hsu <redacted>
---
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
@@ -0,0 +1,81 @@+#!/bin/bash+# SPDX-License-Identifier: GPL-2.0+# Copyright (C) 2026 Harry Hsu <x90613@gmail.com>++.$(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+%insmodtest_modules/$MOD_LIVEPATCH.ko+livepatch:'test_klp_alias_show'and'test_klp_alias_show_alias'resolvetothesameaddress,aliasedsymbolsarenotsupported+insmod:ERROR:couldnotinsertmoduletest_modules/$MOD_LIVEPATCH.ko:Invalidparameters+%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:enablingpatch'$MOD_LIVEPATCH'+livepatch:'$MOD_LIVEPATCH':initializingpatchingtransition+livepatch:'$MOD_LIVEPATCH':startingpatchingtransition+livepatch:'$MOD_LIVEPATCH':completingpatchingtransition+livepatch:'$MOD_LIVEPATCH':patchingcomplete+%insmodtest_modules/$MOD_TARGET.ko+livepatch:'test_klp_alias_show'and'test_klp_alias_show_alias'resolvetothesameaddress,aliasedsymbolsarenotsupported+livepatch:failedtoinitializepatch'$MOD_LIVEPATCH'formodule'$MOD_TARGET'(-22)+livepatch:patch'$MOD_LIVEPATCH'failedformodule'$MOD_TARGET',refusingtoloadmodule'$MOD_TARGET'+insmod:ERROR:couldnotinsertmoduletest_modules/$MOD_TARGET.ko:Invalidparameters+%echo0>$SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled+livepatch:'$MOD_LIVEPATCH':initializingunpatchingtransition+livepatch:'$MOD_LIVEPATCH':startingunpatchingtransition+livepatch:'$MOD_LIVEPATCH':completingunpatchingtransition+livepatch:'$MOD_LIVEPATCH':unpatchingcomplete+%rmmod$MOD_LIVEPATCH"++exit0
@@ -0,0 +1,62 @@+// SPDX-License-Identifier: GPL-2.0+// Copyright (C) 2026 Harry Hsu <x90613@gmail.com>++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/module.h>+#include<linux/kernel.h>+#include<linux/livepatch.h>+#include<linux/seq_file.h>++staticintlivepatch_alias_show(structseq_file*m,void*v)+{+seq_printf(m,"%s: %s\n",THIS_MODULE->name,+"this has been live patched");+return0;+}++/*+*Bothnamesresolvetooneaddress,sotheyenduponasingle+*ops->func_stackandtheredirectionwouldbeambiguous.Loadingthis+*livepatchisexpectedtofail.+*/+staticstructklp_funcfuncs[]={+{+.old_name="test_klp_alias_show",+.new_func=livepatch_alias_show,+},+{+.old_name="test_klp_alias_show_alias",+.new_func=livepatch_alias_show,+},+{},+};++staticstructklp_objectobjs[]={+{+.name="test_klp_alias_target",+.funcs=funcs,+},+{},+};++staticstructklp_patchpatch={+.mod=THIS_MODULE,+.objs=objs,+};++staticinttest_klp_alias_patch_init(void)+{+returnklp_enable_patch(&patch);+}++staticvoidtest_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 <x90613@gmail.com>");+MODULE_DESCRIPTION("Livepatch test: patch two aliased symbols of one object");
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 12:04:14
klp_free_object_loaded() is supposed to clear changes made by
klp_init_object_loaded(). It should call klp_clear_object_relocs()
which is currently defined later.
Move the code for updating object relocations up.
This is just a preparation step. No functional changes.
Signed-off-by: Petr Mladek <pmladek@suse.com>
---
kernel/livepatch/core.c | 72 ++++++++++++++++++++---------------------
1 file changed, 36 insertions(+), 36 deletions(-)
@@ -342,6 +342,42 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,secndx,objname,true);}+staticintklp_write_object_relocs(structklp_patch*patch,+structklp_object*obj,+boolapply)+{+inti,ret;+structklp_modinfo*info=patch->mod->klp_info;++for(i=1;i<info->hdr.e_shnum;i++){+Elf_Shdr*sec=info->sechdrs+i;++if(!(sec->sh_flags&SHF_RELA_LIVEPATCH))+continue;++ret=klp_write_section_relocs(patch->mod,info->sechdrs,+info->secstrings,+patch->mod->core_kallsyms.strtab,+info->symndx,i,obj->name,apply);+if(ret)+returnret;+}++return0;+}++staticintklp_apply_object_relocs(structklp_patch*patch,+structklp_object*obj)+{+returnklp_write_object_relocs(patch,obj,true);+}++staticvoidklp_clear_object_relocs(structklp_patch*patch,+structklp_object*obj)+{+klp_write_object_relocs(patch,obj,false);+}+/**SysfsInterface*
@@ -823,42 +859,6 @@ static int klp_init_func(struct klp_object *obj, struct klp_func *func)func->old_sympos?func->old_sympos:1);}-staticintklp_write_object_relocs(structklp_patch*patch,-structklp_object*obj,-boolapply)-{-inti,ret;-structklp_modinfo*info=patch->mod->klp_info;--for(i=1;i<info->hdr.e_shnum;i++){-Elf_Shdr*sec=info->sechdrs+i;--if(!(sec->sh_flags&SHF_RELA_LIVEPATCH))-continue;--ret=klp_write_section_relocs(patch->mod,info->sechdrs,-info->secstrings,-patch->mod->core_kallsyms.strtab,-info->symndx,i,obj->name,apply);-if(ret)-returnret;-}--return0;-}--staticintklp_apply_object_relocs(structklp_patch*patch,-structklp_object*obj)-{-returnklp_write_object_relocs(patch,obj,true);-}--staticvoidklp_clear_object_relocs(structklp_patch*patch,-structklp_object*obj)-{-klp_write_object_relocs(patch,obj,false);-}-/* parts of the initialization that is done only when the object is loaded */staticintklp_init_object_loaded(structklp_patch*patch,structklp_object*obj)
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 12:04:27
When a module is loaded, klp_module_coming() updates all enabled
livepatches. If an error occurs, it delegates cleanup to
klp_cleanup_module_patches_limited(). However, this cleanup loop skips
the partially updated patch, leaving any changes made prior to failure
unreverted.
One unhandled failure path occurs inside klp_apply_object_relocs(). On
architectures like x86_64, apply_relocate_add() performs a verification
step using memcmp() to check that memory contains the expected relocated
or zeroed value. If relocations left behind by a failed patch are not
cleared, subsequent patch operations or reloads can fail this validation.
Introduce klp_write_object_relocs_limited() to unwind and clear only the
relocations that were successfully applied before klp_write_object_relocs()
encountered an error.
There is no need to clear relocations for other objects in the failing
patch because klp_module_coming() operates strictly on the specific
module being loaded.
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/r/20260830175608.4BABB1F000E9@smtp.kernel.org
Signed-off-by: Petr Mladek <pmladek@suse.com>
---
kernel/livepatch/core.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 12:04:41
When loading a module, klp_module_coming() updates all enabled patches.
If an error occurs, klp_cleanup_module_patches_limited() cleans up fully
processed patches, but skips the patch that failed midway.
The current code is a bit messy. The changes done by
klp_init_object_loaded() should get cleared by klp_free_object_loaded().
But this function also clears obj->mod which is set by
klp_module_coming(). And relocations are cleared separately.
Fix the situations by updating klp_free_object_loaded(). It should
revert all and only changes made by klp_init_object_loaded().
This requires some shuffling:
+ Clear obj->mod explicitly in klp_cleanup_module_patches_limited()
and do not rely on klp_free_object_loaded().
+ Clear relocations in klp_free_object_loaded(). Remove the explicit
call from klp_cleanup_module_patches_limited(). This requires
adding the @patch parameter.
Next, klp_init_object_loaded() has to clear its own changes on
failure. It just returns an error when relocations failed because
they clear their own mess. It could call klp_free_object_loaded()
in other situations because all relocations were done and other
values are just cleared.
Finally, in klp_module_coming(), avoid code duplication by goto targets.
There is no need to clear relocations for other objects in the failing
patch because klp_module_coming() operates strictly on the specific
module being loaded.
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/r/20260823062313.1321B1F000E9@smtp.kernel.org
Closes: https://lore.kernel.org/r/20260830175608.4BABB1F000E9@smtp.kernel.org
Acked-by: Song Liu <song@kernel.org>
Signed-off-by: Petr Mladek <pmladek@suse.com>
---
kernel/livepatch/core.c | 37 +++++++++++++++++++++++++------------
1 file changed, 25 insertions(+), 12 deletions(-)
@@ -738,18 +738,20 @@ static void __klp_free_funcs(struct klp_object *obj, bool nops_only)}/* Clean up when a patched object is unloaded */-staticvoidklp_free_object_loaded(structklp_object*obj)+staticvoidklp_free_object_loaded(structklp_patch*patch,+structklp_object*obj){structklp_func*func;-obj->mod=NULL;-klp_for_each_func(obj,func){func->old_func=NULL;if(func->nop)func->new_func=NULL;}++if(klp_is_module(obj))+klp_clear_object_relocs(patch,obj);}staticvoid__klp_free_objects(structklp_patch*patch,boolnops_only)
@@ -896,7 +898,7 @@ static int klp_init_object_loaded(struct klp_patch *patch,func->old_sympos,(unsignedlong*)&func->old_func);if(ret)-returnret;+gotoerr;/**Aliasedsymbolsshareoneaddress,sotheywouldresolveto
@@ -909,7 +911,8 @@ static int klp_init_object_loaded(struct klp_patch *patch,if(prev_func->old_func==func->old_func){pr_err("'%s' and '%s' resolve to the same address, aliased symbols are not supported\n",prev_func->old_name,func->old_name);-return-EINVAL;+ret=-EINVAL;+gotoerr;}}
@@ -918,7 +921,8 @@ static int klp_init_object_loaded(struct klp_patch *patch,if(!ret){pr_err("kallsyms size lookup failed for '%s'\n",func->old_name);-return-ENOENT;+ret=-ENOENT;+gotoerr;}if(func->nop)
@@ -929,11 +933,17 @@ static int klp_init_object_loaded(struct klp_patch *patch,if(!ret){pr_err("kallsyms size lookup failed for '%s' replacement\n",func->old_name);-return-ENOENT;+ret=-ENOENT;+gotoerr;}}return0;++err:+klp_free_object_loaded(patch,obj);++returnret;}staticintklp_init_object(structklp_patch*patch,structklp_object*obj)
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Atomic replace patches will fail to load if previous patches targeted aliased functions.
--
commit e5553650270f93e11fe885d264a3829802223d3a
Author: Harry Hsu [off-list ref]
livepatch: Fail object initialization on duplicate patched function
This commit introduces a check to prevent patching multiple aliases of a
function within a single livepatch. It fails object initialization if aliases
resolving to the same address are detected.
@@ -885,6 +885,21 @@ static int klp_init_object_loaded(struct klp_patch *patch, if (ret) return ret;+ /*+ * Aliased symbols share one address, so they would resolve to+ * the same klp_ops and stack up on a single ops->func_stack,+ * leaving the redirection ambiguous.+ */+ klp_for_each_func(obj, prev_func) {+ if (prev_func == func)+ break;+ if (prev_func->old_func == func->old_func) {+ pr_err("'%s' and '%s' resolve to the same address, aliased symbols are not supported\n",+ prev_func->old_name, func->old_name);+ return -EINVAL;
[Severity: High]
Will this check inadvertently reject nop functions generated by an atomic
replace patch when previous patches targeted aliased functions?
If previously loaded livepatches targeted aliased functions (for example, one
patch targets __do_sys_fork and another targets __x64_sys_fork), they were
permitted since they resided in different patches.
When an atomic replace patch is loaded, klp_add_nops() loops over all
previously patched functions and adds nops for them to the new patch. Since
nop deduplication is based on old_name, nops for both aliases are added.
Later, this check in klp_init_object_loaded() resolves their addresses and
finds duplicate old_func addresses. This causes the initialization to fail
with -EINVAL, which prevents the system from atomically replacing existing
livepatches and might force a reboot if the old patches cannot be safely
disabled manually.
+ }
+ }
+
ret = kallsyms_lookup_size_offset((unsigned long)func->old_func,
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Off-by-one error in the rollback loop leaves the failing section's partially applied relocations uncleared.
--
commit d297505bb4b1d833ec93725770d65ac1044abcad
Author: Petr Mladek [off-list ref]
livepatch: Clear relocations when klp_apply_object_relocs() fails
This commit introduces klp_write_object_relocs_limited() to unwind and
clear only the relocations that were successfully applied before
klp_write_object_relocs() encountered an error. This prevents partially
applied patches from leaving un-reverted changes that cause subsequent
patch operations to fail validation.
Link: https://lore.kernel.org/r/20260830175608.4BABB1F000E9@smtp.kernel.org
@@ -342,14 +342,17 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
[ ... ]
+static int klp_write_object_relocs_limited(struct klp_patch *patch,
+ struct klp_object *obj,
+ bool apply, int limit)
{
int i, ret;
struct klp_modinfo *info = patch->mod->klp_info;
- for (i = 1; i < info->hdr.e_shnum; i++) {
+ if (!limit || limit > info->hdr.e_shnum)
+ limit = info->hdr.e_shnum;
+
+ for (i = 1; i < limit; i++) {
Elf_Shdr *sec = info->sechdrs + i;
[ ... ]
- if (ret)
+ if (ret) {
+ if (apply)
+ klp_write_object_relocs_limited(patch, obj, false, i);
[Severity: Medium]
When an error occurs in klp_write_section_relocs(), the rollback is triggered
with the failing section index passed as the limit. Since the rollback loop
iterates with a strict less-than bounds check against this limit, will this
skip the rollback for any relocations that were successfully applied within
the failing section itself before it encountered the error?
It looks like this could leave partially applied relocations permanently
in memory within the failing section, causing future module loads to fail
validation.
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Partial relocations are not reverted when apply_relocate_add() fails midway, leading to permanent state corruption.
--
commit 3f6df1b4ed699e839abbdc8919a6c7de5d1850c8
Author: Petr Mladek [off-list ref]
livepatch: Clean up klp_init_object_loaded() when fails
When loading a module, klp_module_coming() updates all enabled patches.
If an error occurs, klp_cleanup_module_patches_limited() cleans up fully
processed patches, but skips the patch that failed midway. This commit
updates klp_free_object_loaded() to revert all and only changes made by
klp_init_object_loaded().
Closes: https://lore.kernel.org/r/20260823062313.1321B1F000E9@smtp.kernel.org
Closes: https://lore.kernel.org/r/20260830175608.4BABB1F000E9@smtp.kernel.org
Next, klp_init_object_loaded() has to clear its own changes on
failure. It just returns an error when relocations failed because
they clear their own mess. It could call klp_free_object_loaded()
in other situations because all relocations were done and other
values are just cleared.
[Severity: High]
Does this actually clear its own mess completely? Looking at how relocations
are rolled back in klp_write_object_relocs_limited(), which was introduced
earlier in this series:
kernel/livepatch/core.c:klp_write_object_relocs_limited() {
...
ret = klp_write_section_relocs(patch->mod, info->sechdrs,
info->secstrings,
patch->mod->core_kallsyms.strtab,
info->symndx, i, obj->name, apply);
if (ret) {
if (apply)
klp_write_object_relocs_limited(patch, obj, false, i);
return ret;
}
...
}
By passing i as the limit to the rollback function, the rollback loop:
for (i = 1; i < limit; i++) {
will terminate at i - 1. Doesn't this skip the rollback for the partially
applied section i?
If apply_relocate_add succeeds on relocations 0 to k-1 in section i, but
fails on relocation k, these partial relocations remain permanently applied
in the livepatch module's text.
Subsequent attempts to load the target module will fail immediately at
relocation 0 due to non-zero target memory, converting a potentially transient
address-space layout failure into a permanent -ENOEXEC on retry.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908120325.299649-1-pmladek@suse.com?part=5
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 13:02:18
On Tue 2026-09-08 12:17:44, sashiko-bot@kernel.org wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Atomic replace patches will fail to load if previous patches targeted aliased functions.
--
commit e5553650270f93e11fe885d264a3829802223d3a
Author: Harry Hsu [off-list ref]
livepatch: Fail object initialization on duplicate patched function
This commit introduces a check to prevent patching multiple aliases of a
function within a single livepatch. It fails object initialization if aliases
resolving to the same address are detected.
@@ -885,6 +885,21 @@ static int klp_init_object_loaded(struct klp_patch *patch, if (ret) return ret;+ /*+ * Aliased symbols share one address, so they would resolve to+ * the same klp_ops and stack up on a single ops->func_stack,+ * leaving the redirection ambiguous.+ */+ klp_for_each_func(obj, prev_func) {+ if (prev_func == func)+ break;+ if (prev_func->old_func == func->old_func) {+ pr_err("'%s' and '%s' resolve to the same address, aliased symbols are not supported\n",+ prev_func->old_name, func->old_name);+ return -EINVAL;
[Severity: High]
Will this check inadvertently reject nop functions generated by an atomic
replace patch when previous patches targeted aliased functions?
If previously loaded livepatches targeted aliased functions (for example, one
patch targets __do_sys_fork and another targets __x64_sys_fork), they were
permitted since they resided in different patches.
When an atomic replace patch is loaded, klp_add_nops() loops over all
previously patched functions and adds nops for them to the new patch. Since
nop deduplication is based on old_name, nops for both aliases are added.
Later, this check in klp_init_object_loaded() resolves their addresses and
finds duplicate old_func addresses. This causes the initialization to fail
with -EINVAL, which prevents the system from atomically replacing existing
livepatches and might force a reboot if the old patches cannot be safely
disabled manually.
Wow, this is a nice catch. I would say that it is a corner case but
the problem seems to exist. Now, the question is how to deal with it.
I see three possibilities:
1. We might detect the aliases in klp_find_func() and create only
one "nop" entry or do not create it all.
2. The problem might be easier to solve after adding
the provides/obsoletes, aka replace set, feature [1].
It should prevent loading the other livepatch for the aliased
symbol in the 1st place.
3. Just document the problem as a limitation. It might be hit only
when installing more liveapatches in parallel that current
wild way.
I personally prefer to wait for the provides/requires feature.
[1] https://lore.kernel.org/all/20260607131659.29281-1-laoar.shao@gmail.com/
Best Regards,
Petr
quoted
+ }
+ }
+
ret = kallsyms_lookup_size_offset((unsigned long)func->old_func,
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 13:29:27
On Tue 2026-09-08 12:18:10, sashiko-bot@kernel.org wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Off-by-one error in the rollback loop leaves the failing section's partially applied relocations uncleared.
--
commit d297505bb4b1d833ec93725770d65ac1044abcad
Author: Petr Mladek [off-list ref]
livepatch: Clear relocations when klp_apply_object_relocs() fails
This commit introduces klp_write_object_relocs_limited() to unwind and
clear only the relocations that were successfully applied before
klp_write_object_relocs() encountered an error. This prevents partially
applied patches from leaving un-reverted changes that cause subsequent
patch operations to fail validation.
Link: https://lore.kernel.org/r/20260830175608.4BABB1F000E9@smtp.kernel.org
@@ -342,14 +342,17 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
[ ... ]
quoted
+static int klp_write_object_relocs_limited(struct klp_patch *patch,
+ struct klp_object *obj,
+ bool apply, int limit)
{
int i, ret;
struct klp_modinfo *info = patch->mod->klp_info;
- for (i = 1; i < info->hdr.e_shnum; i++) {
+ if (!limit || limit > info->hdr.e_shnum)
+ limit = info->hdr.e_shnum;
+
+ for (i = 1; i < limit; i++) {
Elf_Shdr *sec = info->sechdrs + i;
[ ... ]
quoted
- if (ret)
+ if (ret) {
+ if (apply)
+ klp_write_object_relocs_limited(patch, obj, false, i);
[Severity: Medium]
When an error occurs in klp_write_section_relocs(), the rollback is triggered
with the failing section index passed as the limit. Since the rollback loop
iterates with a strict less-than bounds check against this limit, will this
skip the rollback for any relocations that were successfully applied within
the failing section itself before it encountered the error?
It looks like this could leave partially applied relocations permanently
in memory within the failing section, causing future module loads to fail
validation.
Sigh, I did not go deep enough. And it goes down to the arch-specific
implementations, in particular arch/x86/kernel/module.c:
static int __write_relocate_add(Elf64_Shdr *sechdrs,
const char *strtab,
unsigned int symindex,
unsigned int relsec,
struct module *me,
void *(*write)(void *dest, const void *src, size_t len),
bool apply)
{
[...]
for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
[...]
if (memcmp(loc, &zero, size)) {
pr_err("x86/modules: Invalid relocation target, existing value is nonzero for sec %u, idx %u, type %d, loc %lx, val %llx\n",
relsec, i, (int)ELF64_R_TYPE(rel[i].r_info),
(unsigned long)loc, val);
return -ENOEXEC;
}
write(loc, &val, size);
} else {
if (memcmp(loc, &val, size)) {
pr_warn("x86/modules: Invalid relocation target, existing value does not match expected value for sec %u, idx %u, type %d, loc %lx, val %llx\n",
relsec, i, (int)ELF64_R_TYPE(rel[i].r_info),
(unsigned long)loc, val);
return -ENOEXEC;
}
write(loc, &zero, size);
}
[...]
We would need to implement the revert at this level.
IMHO, it could be done separately.
I used Gemini LLM to check it and it seems that x86_64 is the only
architecture with these permissive checks. It seems that all other
architectures just write the new value without checking
the existing one.
Would you go this way and try to get patch into x86 code?
Should I do it in v5 or separately?
Best Regards,
Petr
PS: I would prefer to go with unless it causes regression
and fix this "later". We could not endlessly delay
fixes because of Sashiko finding pre-existing problems.
From: Petr Mladek <pmladek@suse.com> Date: 2026-09-08 13:32:21
On Tue 2026-09-08 12:25:48, sashiko-bot@kernel.org wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Partial relocations are not reverted when apply_relocate_add() fails midway, leading to permanent state corruption.
--
commit 3f6df1b4ed699e839abbdc8919a6c7de5d1850c8
Author: Petr Mladek [off-list ref]
livepatch: Clean up klp_init_object_loaded() when fails
When loading a module, klp_module_coming() updates all enabled patches.
If an error occurs, klp_cleanup_module_patches_limited() cleans up fully
processed patches, but skips the patch that failed midway. This commit
updates klp_free_object_loaded() to revert all and only changes made by
klp_init_object_loaded().
Closes: https://lore.kernel.org/r/20260823062313.1321B1F000E9@smtp.kernel.org
Closes: https://lore.kernel.org/r/20260830175608.4BABB1F000E9@smtp.kernel.org
quoted
Next, klp_init_object_loaded() has to clear its own changes on
failure. It just returns an error when relocations failed because
they clear their own mess. It could call klp_free_object_loaded()
in other situations because all relocations were done and other
values are just cleared.
[Severity: High]
Does this actually clear its own mess completely? Looking at how relocations
are rolled back in klp_write_object_relocs_limited(), which was introduced
earlier in this series:
kernel/livepatch/core.c:klp_write_object_relocs_limited() {
...
ret = klp_write_section_relocs(patch->mod, info->sechdrs,
info->secstrings,
patch->mod->core_kallsyms.strtab,
info->symndx, i, obj->name, apply);
if (ret) {
if (apply)
klp_write_object_relocs_limited(patch, obj, false, i);
return ret;
}
...
}
By passing i as the limit to the rollback function, the rollback loop:
for (i = 1; i < limit; i++) {
will terminate at i - 1. Doesn't this skip the rollback for the partially
applied section i?
If apply_relocate_add succeeds on relocations 0 to k-1 in section i, but
fails on relocation k, these partial relocations remain permanently applied
in the livepatch module's text.
This is the same problem which was reported for the 4th patch.
It would need to handled in the arch-code, namely
in __write_relocate_add() in arch/x86/kernel/module.c.
Best Regards,
Petr
Subsequent attempts to load the target module will fail immediately at
relocation 0 due to non-zero target memory, converting a potentially transient
address-space layout failure into a permanent -ENOEXEC on retry.
From: Miroslav Benes <mbenes@suse.cz> Date: 2026-09-18 12:54:10
On Tue, 8 Sep 2026, Petr Mladek wrote:
On Tue 2026-09-08 12:17:44, sashiko-bot@kernel.org wrote:
quoted
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Atomic replace patches will fail to load if previous patches targeted aliased functions.
--
commit e5553650270f93e11fe885d264a3829802223d3a
Author: Harry Hsu [off-list ref]
livepatch: Fail object initialization on duplicate patched function
This commit introduces a check to prevent patching multiple aliases of a
function within a single livepatch. It fails object initialization if aliases
resolving to the same address are detected.
@@ -885,6 +885,21 @@ static int klp_init_object_loaded(struct klp_patch *patch, if (ret) return ret;+ /*+ * Aliased symbols share one address, so they would resolve to+ * the same klp_ops and stack up on a single ops->func_stack,+ * leaving the redirection ambiguous.+ */+ klp_for_each_func(obj, prev_func) {+ if (prev_func == func)+ break;+ if (prev_func->old_func == func->old_func) {+ pr_err("'%s' and '%s' resolve to the same address, aliased symbols are not supported\n",+ prev_func->old_name, func->old_name);+ return -EINVAL;
[Severity: High]
Will this check inadvertently reject nop functions generated by an atomic
replace patch when previous patches targeted aliased functions?
If previously loaded livepatches targeted aliased functions (for example, one
patch targets __do_sys_fork and another targets __x64_sys_fork), they were
permitted since they resided in different patches.
When an atomic replace patch is loaded, klp_add_nops() loops over all
previously patched functions and adds nops for them to the new patch. Since
nop deduplication is based on old_name, nops for both aliases are added.
Later, this check in klp_init_object_loaded() resolves their addresses and
finds duplicate old_func addresses. This causes the initialization to fail
with -EINVAL, which prevents the system from atomically replacing existing
livepatches and might force a reboot if the old patches cannot be safely
disabled manually.
Wow, this is a nice catch. I would say that it is a corner case but
the problem seems to exist. Now, the question is how to deal with it.
I see three possibilities:
1. We might detect the aliases in klp_find_func() and create only
one "nop" entry or do not create it all.
2. The problem might be easier to solve after adding
the provides/obsoletes, aka replace set, feature [1].
It should prevent loading the other livepatch for the aliased
symbol in the 1st place.
3. Just document the problem as a limitation. It might be hit only
when installing more liveapatches in parallel that current
wild way.
I personally prefer to wait for the provides/requires feature.
Perhaps.
Could we also check prev_func/func->nop in the check in
klp_init_object_loaded() and have a pass if true?
Miroslav
From: Miroslav Benes <mbenes@suse.cz> Date: 2026-09-18 13:40:33
On Tue, 08 Sep 2026 14:03:22 +0200, Petr Mladek [off-list ref] wrote:
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.
[...]
Acked-by: Miroslav Benes <mbenes@suse.cz>
--
Miroslav
From: Miroslav Benes <mbenes@suse.cz> Date: 2026-09-18 13:40:42
On Tue, 08 Sep 2026 14:03:23 +0200, Petr Mladek [off-list ref] wrote:
klp_free_object_loaded() is supposed to clear changes made by
klp_init_object_loaded(). It should call klp_clear_object_relocs()
which is currently defined later.
Move the code for updating object relocations up.
This is just a preparation step. No functional changes.
[...]
Acked-by: Miroslav Benes <mbenes@suse.cz>
--
Miroslav
From: Miroslav Benes <mbenes@suse.cz> Date: 2026-09-18 14:00:09
On Tue, 8 Sep 2026, Petr Mladek wrote:
When a module is loaded, klp_module_coming() updates all enabled
livepatches. If an error occurs, it delegates cleanup to
klp_cleanup_module_patches_limited(). However, this cleanup loop skips
the partially updated patch, leaving any changes made prior to failure
unreverted.
One unhandled failure path occurs inside klp_apply_object_relocs(). On
architectures like x86_64, apply_relocate_add() performs a verification
step using memcmp() to check that memory contains the expected relocated
or zeroed value. If relocations left behind by a failed patch are not
cleared, subsequent patch operations or reloads can fail this validation.
Introduce klp_write_object_relocs_limited() to unwind and clear only the
relocations that were successfully applied before klp_write_object_relocs()
encountered an error.
There is no need to clear relocations for other objects in the failing
patch because klp_module_coming() operates strictly on the specific
module being loaded.
Hm, I spent some quite time on that and I am not sure if I deciphered
everything correctly.
It seems to me that all error handling in those paths you are mentioning
above is correct and the only problematic thing is that
klp_apply_object_relocs() in klp_init_object_loaded() returns ret
immediately which leaves partially applied relocations in case of an
error. Following calls in klp_init_object_loaded() go through err: label
where klp_free_object_loaded() is called which should be fine.
Is it correct?
Wouldn't be better to be somehow consistent and clean up right in the
error path of klp_apply_object_relocs() call in klp_init_object_loaded()
similarly to what is already there. After all we want to clean the object.
But I also see why you want to do it this way. It only seems more fragile
to me.
Miroslav
From: Miroslav Benes <mbenes@suse.cz> Date: 2026-09-18 14:05:19
On Fri, 18 Sep 2026, Miroslav Benes wrote:
On Tue, 8 Sep 2026, Petr Mladek wrote:
quoted
When a module is loaded, klp_module_coming() updates all enabled
livepatches. If an error occurs, it delegates cleanup to
klp_cleanup_module_patches_limited(). However, this cleanup loop skips
the partially updated patch, leaving any changes made prior to failure
unreverted.
One unhandled failure path occurs inside klp_apply_object_relocs(). On
architectures like x86_64, apply_relocate_add() performs a verification
step using memcmp() to check that memory contains the expected relocated
or zeroed value. If relocations left behind by a failed patch are not
cleared, subsequent patch operations or reloads can fail this validation.
Introduce klp_write_object_relocs_limited() to unwind and clear only the
relocations that were successfully applied before klp_write_object_relocs()
encountered an error.
There is no need to clear relocations for other objects in the failing
patch because klp_module_coming() operates strictly on the specific
module being loaded.
Hm, I spent some quite time on that and I am not sure if I deciphered
everything correctly.
It seems to me that all error handling in those paths you are mentioning
above is correct and the only problematic thing is that
klp_apply_object_relocs() in klp_init_object_loaded() returns ret
immediately which leaves partially applied relocations in case of an
error. Following calls in klp_init_object_loaded() go through err: label
where klp_free_object_loaded() is called which should be fine.
Is it correct?
Wouldn't be better to be somehow consistent and clean up right in the
error path of klp_apply_object_relocs() call in klp_init_object_loaded()
similarly to what is already there. After all we want to clean the object.
But I also see why you want to do it this way. It only seems more fragile
to me.
And only now I realized that I checked with 5/5 already applied by
mistake. The above still stands though and at least I reviewed 5/5 as
well.
Miroslav
From: Miroslav Benes <mbenes@suse.cz> Date: 2026-09-18 14:07:14
On Tue, 08 Sep 2026 14:03:25 +0200, Petr Mladek [off-list ref] wrote:
When loading a module, klp_module_coming() updates all enabled patches.
If an error occurs, klp_cleanup_module_patches_limited() cleans up fully
processed patches, but skips the patch that failed midway.
The current code is a bit messy. The changes done by
klp_init_object_loaded() should get cleared by klp_free_object_loaded().
But this function also clears obj->mod which is set by
klp_module_coming(). And relocations are cleared separately.
[...]
Acked-by: Miroslav Benes <mbenes@suse.cz>
--
Miroslav