Thank you for your contribution! Sashiko AI review found 3 potential issue(s) 
to consider:

Pre-existing issues:
- [High] klp_ftrace_handler() incorrectly executes newly patched code for 
unpatched tasks when multiple aliased functions exist in the same transition 
patch.
- [High] Premature freeing of dynamically allocated NOP functions without RCU 
synchronization leads to a Use-After-Free in klp_ftrace_handler().
- [High] Concurrent module loading or unloading during a livepatch transition 
causes list corruption and Use-After-Free due to lockless klp_ops traversal.
--

--- Patch [1]: [PATCH] livepatch: Fix stack check for aliased old_func ---
commit 3438e93da695fdf6d92c99235da90b29e8a2a08f
Author: Harry Hsu <[email protected]>

livepatch: Fix stack check for aliased old_func

This patch fixes an out-of-bounds read and an infinite -EAGAIN loop in
klp_check_stack_func() when aliased functions share a single func_stack.
It addresses the issue by testing the function's position in the stack
rather than relying on list_is_singular().

> diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
> index 2351a19ac2a9e..8d6e3a58101fc 100644
> --- a/kernel/livepatch/transition.c
> +++ b/kernel/livepatch/transition.c
> @@ -223,7 +223,7 @@ static int klp_check_stack_func(struct klp_func *func, 
> unsigned long *entries,
>                */
>               ops = klp_find_ops(func->old_func);

[Severity: High]
This is a pre-existing issue and was not introduced by the patch under review,
but does klp_find_ops() traverse the global klp_ops list safely here?

Since klp_check_stack_func() can be called during a context switch, it executes
without klp_mutex protection. If a patched module is concurrently unloaded,
klp_module_going() acquires klp_mutex and calls klp_unpatch_func(), which
removes the ops from the list and frees it:

kernel/livepatch/patch.c:klp_unpatch_func() {
        ...
        list_del_rcu(&func->stack_node);
        list_del(&ops->node);
        kfree(ops);
        ...
}

Could this concurrent module unloading race with the lockless traversal in
klp_find_ops() and lead to a use-after-free or list corruption?

>  
> -             if (list_is_singular(&ops->func_stack)) {
> +             if (list_is_last(&func->stack_node, &ops->func_stack)) {

[Severity: High]
This is a pre-existing issue and was not introduced by the patch under review,
but does the same list_is_singular() assumption cause a use-after-free in
klp_unpatch_func()?

When a replacement livepatch overrides only one of several aliased functions
from an older patch, a NOP function is dynamically allocated for the
un-overridden alias. When the transition finishes, klp_discard_nops() triggers
unpatching and immediate freeing of the NOP functions:

kernel/livepatch/core.c:klp_discard_nops() {
        klp_unpatch_objects_dynamic(klp_transition_patch);
        klp_free_objects_dynamic(klp_transition_patch);
}

Since the stack also contains the overridden real function, the check for
list_is_singular() in klp_unpatch_func() evaluates to false, which bypasses
unregister_ftrace_function() and its associated ftrace_sync:

kernel/livepatch/patch.c:klp_unpatch_func() {
        ...
        if (list_is_singular(&ops->func_stack)) {
                /* ... */
        } else {
                list_del_rcu(&func->stack_node);
        }
        ...
}

The NOP is then immediately freed by kfree(). If klp_ftrace_handler() executes
concurrently on another CPU, might it access the freed NOP object?

>                       /* original function */
>                       func_addr = (unsigned long)func->old_func;
>                       func_size = func->old_size;


While reviewing the alias handling, I noticed one more pre-existing issue:

[Severity: High]
This is a pre-existing issue and was not introduced by the patch under review,
but does klp_ftrace_handler() correctly handle unpatched tasks when multiple
aliased functions exist in the same transition patch?

When an unpatched task invokes the intercepted function, klp_ftrace_handler()
observes that the patch is in transition and advances exactly once:

kernel/livepatch/patch.c:klp_ftrace_handler() {
        ...
        if (patch_state == KLP_TRANSITION_UNPATCHED) {
                /*
                 * Use the previously patched version of the function.
                 * If no previous patches exist, continue with the
                 * original function.
                 */
                func = list_entry_rcu(func->stack_node.next,
                                      struct klp_func, stack_node);
        ...
}

If multiple aliases are placed on the same ops->func_stack by the same patch,
won't the next entry just be another alias that is also in transition? Can this
cause the handler to mistakenly execute newly patched code for unpatched tasks?

-- 
Sashiko AI review ยท 
https://sashiko.dev/#/patchset/[email protected]?part=1

Reply via email to