* [PATCH v3 0/2] livepatch: some improvements @ 2025-02-27 2:47 Yafang Shao 2025-02-27 2:47 ` [PATCH v3 1/2] livepatch: Add comment to clarify klp_add_nops() Yafang Shao 2025-02-27 2:47 ` [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU Yafang Shao 0 siblings, 2 replies; 9+ messages in thread From: Yafang Shao @ 2025-02-27 2:47 UTC (permalink / raw) To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence; +Cc: live-patching, Yafang Shao #1: Clarify klp_add_nops() #2: Replace the tasklist_lock with RCU in the KLP transition v2->v3: - Skip the newly fokred tasks during klp_check_and_switch_task() (Josh) - Fix the local variable patch_state in the ftrace handler (Josh) v1->v2: https://lore.kernel.org/live-patching/20250223062046.2943-1-laoar.shao@gmail.com/ - Enhance the comment in #1 for better clarity and detail. (Petr) - Replace the tasklist_lock with RCU (Josh) - Remove the fix for RCU warnings as the root cause is currently unclear. Once the root cause is identified, I will submit the fix separately. v1: https://lore.kernel.org/live-patching/20250211062437.46811-1-laoar.shao@gmail.com/ Yafang Shao (2): livepatch: Add comment to clarify klp_add_nops() livepatch: Replace tasklist_lock with RCU include/linux/livepatch.h | 4 ++-- kernel/fork.c | 2 +- kernel/livepatch/core.c | 9 ++++++--- kernel/livepatch/patch.c | 8 +++++++- kernel/livepatch/transition.c | 35 ++++++++++++++--------------------- kernel/livepatch/transition.h | 1 + 6 files changed, 31 insertions(+), 28 deletions(-) -- 2.43.5 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 1/2] livepatch: Add comment to clarify klp_add_nops() 2025-02-27 2:47 [PATCH v3 0/2] livepatch: some improvements Yafang Shao @ 2025-02-27 2:47 ` Yafang Shao 2025-02-27 14:45 ` Petr Mladek 2025-02-27 2:47 ` [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU Yafang Shao 1 sibling, 1 reply; 9+ messages in thread From: Yafang Shao @ 2025-02-27 2:47 UTC (permalink / raw) To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence; +Cc: live-patching, Yafang Shao Add detailed comments to clarify the purpose of klp_add_nops() function. These comments are based on Petr's explanation[0]. Link: https://lore.kernel.org/all/Z6XUA7D0eU_YDMVp@pathway.suse.cz/ [0] Suggested-by: Petr Mladek <pmladek@suse.com> Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org> Signed-off-by: Yafang Shao <laoar.shao@gmail.com> --- kernel/livepatch/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 0cd39954d5a1..4a0fb7978d0d 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -601,9 +601,12 @@ static int klp_add_object_nops(struct klp_patch *patch, } /* - * Add 'nop' functions which simply return to the caller to run - * the original function. The 'nop' functions are added to a - * patch to facilitate a 'replace' mode. + * Add 'nop' functions which simply return to the caller to run the + * original function. + * + * They are added only when the atomic replace mode is used and only for + * functions which are currently livepatched but are no longer included + * in the new livepatch. */ static int klp_add_nops(struct klp_patch *patch) { -- 2.43.5 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/2] livepatch: Add comment to clarify klp_add_nops() 2025-02-27 2:47 ` [PATCH v3 1/2] livepatch: Add comment to clarify klp_add_nops() Yafang Shao @ 2025-02-27 14:45 ` Petr Mladek 2025-03-04 15:48 ` Petr Mladek 0 siblings, 1 reply; 9+ messages in thread From: Petr Mladek @ 2025-02-27 14:45 UTC (permalink / raw) To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, live-patching On Thu 2025-02-27 10:47:32, Yafang Shao wrote: > Add detailed comments to clarify the purpose of klp_add_nops() function. > These comments are based on Petr's explanation[0]. > > Link: https://lore.kernel.org/all/Z6XUA7D0eU_YDMVp@pathway.suse.cz/ [0] > Suggested-by: Petr Mladek <pmladek@suse.com> > Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org> > Signed-off-by: Yafang Shao <laoar.shao@gmail.com> Reviewed-by: Petr Mladek <pmladek@suse.com> Best Regards, Petr ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 1/2] livepatch: Add comment to clarify klp_add_nops() 2025-02-27 14:45 ` Petr Mladek @ 2025-03-04 15:48 ` Petr Mladek 0 siblings, 0 replies; 9+ messages in thread From: Petr Mladek @ 2025-03-04 15:48 UTC (permalink / raw) To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, live-patching On Thu 2025-02-27 15:45:57, Petr Mladek wrote: > On Thu 2025-02-27 10:47:32, Yafang Shao wrote: > > Add detailed comments to clarify the purpose of klp_add_nops() function. > > These comments are based on Petr's explanation[0]. > > > > Link: https://lore.kernel.org/all/Z6XUA7D0eU_YDMVp@pathway.suse.cz/ [0] > > Suggested-by: Petr Mladek <pmladek@suse.com> > > Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org> > > Signed-off-by: Yafang Shao <laoar.shao@gmail.com> > > Reviewed-by: Petr Mladek <pmladek@suse.com> JFYI, I have pushed this patch into livepatching.git, branch for-6.15/trivial. It is a trivial patch and is independent on the other problems. Best Regards, Petr ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU 2025-02-27 2:47 [PATCH v3 0/2] livepatch: some improvements Yafang Shao 2025-02-27 2:47 ` [PATCH v3 1/2] livepatch: Add comment to clarify klp_add_nops() Yafang Shao @ 2025-02-27 2:47 ` Yafang Shao 2025-02-27 16:22 ` Petr Mladek 2025-03-04 14:43 ` Miroslav Benes 1 sibling, 2 replies; 9+ messages in thread From: Yafang Shao @ 2025-02-27 2:47 UTC (permalink / raw) To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence; +Cc: live-patching, Yafang Shao The tasklist_lock in the KLP transition might result high latency under specific workload. We can replace it with RCU. After a new task is forked, its kernel stack is always set to empty[0]. Therefore, we can init these new tasks to KLP_TRANSITION_IDLE state after they are forked. If these tasks are forked during the KLP transition but before klp_check_and_switch_task(), they can be safely skipped during klp_check_and_switch_task(). Additionally, if klp_ftrace_handler() is triggered right after forking, the task can determine which function to use based on the klp_target_state. With the above change, we can safely convert the tasklist_lock to RCU. Link: https://lore.kernel.org/all/20250213173253.ovivhuq2c5rmvkhj@jpoimboe/ [0] Link: https://lore.kernel.org/all/20250214181206.xkvxohoc4ft26uhf@jpoimboe/ [1] Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org> Signed-off-by: Yafang Shao <laoar.shao@gmail.com> --- include/linux/livepatch.h | 4 ++-- kernel/fork.c | 2 +- kernel/livepatch/patch.c | 8 +++++++- kernel/livepatch/transition.c | 35 ++++++++++++++--------------------- kernel/livepatch/transition.h | 1 + 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 51a258c24ff5..41c424120f49 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -198,7 +198,7 @@ int klp_enable_patch(struct klp_patch *); int klp_module_coming(struct module *mod); void klp_module_going(struct module *mod); -void klp_copy_process(struct task_struct *child); +void klp_init_process(struct task_struct *child); void klp_update_patch_state(struct task_struct *task); static inline bool klp_patch_pending(struct task_struct *task) @@ -241,7 +241,7 @@ static inline int klp_module_coming(struct module *mod) { return 0; } static inline void klp_module_going(struct module *mod) {} static inline bool klp_patch_pending(struct task_struct *task) { return false; } static inline void klp_update_patch_state(struct task_struct *task) {} -static inline void klp_copy_process(struct task_struct *child) {} +static inline void klp_init_process(struct task_struct *child) {} static inline int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs, diff --git a/kernel/fork.c b/kernel/fork.c index 735405a9c5f3..da247c4d5ec5 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2544,7 +2544,7 @@ __latent_entropy struct task_struct *copy_process( p->exit_signal = args->exit_signal; } - klp_copy_process(p); + klp_init_process(p); sched_core_fork(p); diff --git a/kernel/livepatch/patch.c b/kernel/livepatch/patch.c index 90408500e5a3..3da98877c785 100644 --- a/kernel/livepatch/patch.c +++ b/kernel/livepatch/patch.c @@ -95,7 +95,13 @@ static void notrace klp_ftrace_handler(unsigned long ip, patch_state = current->patch_state; - WARN_ON_ONCE(patch_state == KLP_TRANSITION_IDLE); + /* If the patch_state is KLP_TRANSITION_IDLE, it means the task + * was forked after klp_init_transition(). In this case, the + * newly forked task can determine which function to use based + * on the klp_target_state. + */ + if (patch_state == KLP_TRANSITION_IDLE) + patch_state = klp_target_state; if (patch_state == KLP_TRANSITION_UNPATCHED) { /* diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index ba069459c101..af76defca67a 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -23,7 +23,7 @@ static DEFINE_PER_CPU(unsigned long[MAX_STACK_ENTRIES], klp_stack_entries); struct klp_patch *klp_transition_patch; -static int klp_target_state = KLP_TRANSITION_IDLE; +int klp_target_state = KLP_TRANSITION_IDLE; static unsigned int klp_signals_cnt; @@ -294,6 +294,14 @@ static int klp_check_and_switch_task(struct task_struct *task, void *arg) { int ret; + /* + * If the patch_state remains KLP_TRANSITION_IDLE at this point, it + * indicates that the task was forked after klp_init_transition(). + * In this case, it is safe to skip the task. + */ + if (!test_tsk_thread_flag(task, TIF_PATCH_PENDING)) + return 0; + if (task_curr(task) && task != current) return -EBUSY; @@ -466,11 +474,11 @@ void klp_try_complete_transition(void) * Usually this will transition most (or all) of the tasks on a system * unless the patch includes changes to a very common function. */ - read_lock(&tasklist_lock); + rcu_read_lock(); for_each_process_thread(g, task) if (!klp_try_switch_task(task)) complete = false; - read_unlock(&tasklist_lock); + rcu_read_unlock(); /* * Ditto for the idle "swapper" tasks. @@ -694,25 +702,10 @@ void klp_reverse_transition(void) } /* Called from copy_process() during fork */ -void klp_copy_process(struct task_struct *child) +void klp_init_process(struct task_struct *child) { - - /* - * The parent process may have gone through a KLP transition since - * the thread flag was copied in setup_thread_stack earlier. Bring - * the task flag up to date with the parent here. - * - * The operation is serialized against all klp_*_transition() - * operations by the tasklist_lock. The only exceptions are - * klp_update_patch_state(current) and __klp_sched_try_switch(), but we - * cannot race with them because we are current. - */ - if (test_tsk_thread_flag(current, TIF_PATCH_PENDING)) - set_tsk_thread_flag(child, TIF_PATCH_PENDING); - else - clear_tsk_thread_flag(child, TIF_PATCH_PENDING); - - child->patch_state = current->patch_state; + clear_tsk_thread_flag(child, TIF_PATCH_PENDING); + child->patch_state = KLP_TRANSITION_IDLE; } /* diff --git a/kernel/livepatch/transition.h b/kernel/livepatch/transition.h index 322db16233de..febcf1d50fc5 100644 --- a/kernel/livepatch/transition.h +++ b/kernel/livepatch/transition.h @@ -5,6 +5,7 @@ #include <linux/livepatch.h> extern struct klp_patch *klp_transition_patch; +extern int klp_target_state; void klp_init_transition(struct klp_patch *patch, int state); void klp_cancel_transition(void); -- 2.43.5 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU 2025-02-27 2:47 ` [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU Yafang Shao @ 2025-02-27 16:22 ` Petr Mladek 2025-02-28 2:38 ` Yafang Shao 2025-03-04 14:43 ` Miroslav Benes 1 sibling, 1 reply; 9+ messages in thread From: Petr Mladek @ 2025-02-27 16:22 UTC (permalink / raw) To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, live-patching On Thu 2025-02-27 10:47:33, Yafang Shao wrote: > The tasklist_lock in the KLP transition might result high latency under > specific workload. We can replace it with RCU. > > After a new task is forked, its kernel stack is always set to empty[0]. > Therefore, we can init these new tasks to KLP_TRANSITION_IDLE state > after they are forked. If these tasks are forked during the KLP > transition but before klp_check_and_switch_task(), they can be safely > skipped during klp_check_and_switch_task(). Additionally, if > klp_ftrace_handler() is triggered right after forking, the task can > determine which function to use based on the klp_target_state. > > With the above change, we can safely convert the tasklist_lock to RCU. > > Link: https://lore.kernel.org/all/20250213173253.ovivhuq2c5rmvkhj@jpoimboe/ [0] > Link: https://lore.kernel.org/all/20250214181206.xkvxohoc4ft26uhf@jpoimboe/ [1] > Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org> > Signed-off-by: Yafang Shao <laoar.shao@gmail.com> > --- > --- a/kernel/livepatch/patch.c > +++ b/kernel/livepatch/patch.c > @@ -95,7 +95,13 @@ static void notrace klp_ftrace_handler(unsigned long ip, > > patch_state = current->patch_state; > > - WARN_ON_ONCE(patch_state == KLP_TRANSITION_IDLE); > + /* If the patch_state is KLP_TRANSITION_IDLE, it means the task > + * was forked after klp_init_transition(). In this case, the > + * newly forked task can determine which function to use based > + * on the klp_target_state. > + */ > + if (patch_state == KLP_TRANSITION_IDLE) > + patch_state = klp_target_state; > CPU0 CPU1 task_0 (forked process): funcA klp_ftrace_handler() if (patch_state == KLP_TRANSITION_IDLE) patch_state = klp_target_state # set to KLP_TRANSITION_PATCHED # redirected to klp_funcA() echo 0 >/sys/kernel/livepatch/patch1/enabled klp_reverse_transition() klp_target_state = !klp_target_state; # set to KLP_TRANSITION_UNPATCHED funcB if (patch_state == KLP_TRANSITION_IDLE) patch_state = klp_target_state # set to KLP_TRANSITION_UNPATCHED # staying in origianl funcB BANG: livepatched and original function called at the same time => broken consistency model. BTW: This is what I tried to warn you about at https://lore.kernel.org/r/Z69Wuhve2vnsrtp_@pathway.suse.cz See below for more: > if (patch_state == KLP_TRANSITION_UNPATCHED) { > /* > diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c > index ba069459c101..af76defca67a 100644 > --- a/kernel/livepatch/transition.c > +++ b/kernel/livepatch/transition.c > @@ -23,7 +23,7 @@ static DEFINE_PER_CPU(unsigned long[MAX_STACK_ENTRIES], klp_stack_entries); > > struct klp_patch *klp_transition_patch; > > -static int klp_target_state = KLP_TRANSITION_IDLE; > +int klp_target_state = KLP_TRANSITION_IDLE; > > static unsigned int klp_signals_cnt; > > @@ -294,6 +294,14 @@ static int klp_check_and_switch_task(struct task_struct *task, void *arg) > { > int ret; > > + /* > + * If the patch_state remains KLP_TRANSITION_IDLE at this point, it > + * indicates that the task was forked after klp_init_transition(). > + * In this case, it is safe to skip the task. > + */ > + if (!test_tsk_thread_flag(task, TIF_PATCH_PENDING)) > + return 0; > + > if (task_curr(task) && task != current) > return -EBUSY; > > @@ -466,11 +474,11 @@ void klp_try_complete_transition(void) > * Usually this will transition most (or all) of the tasks on a system > * unless the patch includes changes to a very common function. > */ > - read_lock(&tasklist_lock); > + rcu_read_lock(); > for_each_process_thread(g, task) > if (!klp_try_switch_task(task)) > complete = false; > - read_unlock(&tasklist_lock); > + rcu_read_unlock(); > > /* > * Ditto for the idle "swapper" tasks. > @@ -694,25 +702,10 @@ void klp_reverse_transition(void) > } > > /* Called from copy_process() during fork */ > -void klp_copy_process(struct task_struct *child) > +void klp_init_process(struct task_struct *child) > { > - > - /* > - * The parent process may have gone through a KLP transition since > - * the thread flag was copied in setup_thread_stack earlier. Bring > - * the task flag up to date with the parent here. > - * > - * The operation is serialized against all klp_*_transition() > - * operations by the tasklist_lock. The only exceptions are > - * klp_update_patch_state(current) and __klp_sched_try_switch(), but we > - * cannot race with them because we are current. > - */ > - if (test_tsk_thread_flag(current, TIF_PATCH_PENDING)) > - set_tsk_thread_flag(child, TIF_PATCH_PENDING); > - else > - clear_tsk_thread_flag(child, TIF_PATCH_PENDING); > - > - child->patch_state = current->patch_state; > + clear_tsk_thread_flag(child, TIF_PATCH_PENDING); > + child->patch_state = KLP_TRANSITION_IDLE; I thought that we might do: child->patch_state = klp_target_state; to avoid the race in the klp_ftrace_handler() described above. But the following might happen: CPU0 CPU1 klp_init_process(child) child->patch_state = KLP_TRANSITION_IDLE klp_enable_patch() # setup ftrace handlers # initialize all processes # in the task list # add "child" into the task list schedule() [running child now] funcA() klp_ftrace_handler() child->patch_state = KLP_TRANSITION_IDLE BANG: Same problem as with the original patch. Hmm, the 2nd version of this patchset tried to do: diff --git a/kernel/livepatch/patch.c b/kernel/livepatch/patch.c index 90408500e5a3..5e523a3fbb3c 100644 --- a/kernel/livepatch/patch.c +++ b/kernel/livepatch/patch.c @@ -95,7 +95,12 @@ static void notrace klp_ftrace_handler(unsigned long ip, patch_state = current->patch_state; - WARN_ON_ONCE(patch_state == KLP_TRANSITION_IDLE); + /* If the patch_state is KLP_TRANSITION_IDLE, it indicates the + * task was forked after klp_init_transition(). For this newly + * forked task, it is safe to switch it to klp_target_state. + */ + if (patch_state == KLP_TRANSITION_IDLE) + current->patch_state = klp_target_state; if (patch_state == KLP_TRANSITION_UNPATCHED) { /* Note: It is broken. It sets current->patch_state but it later checks the local variable "patch_state" which is still KLP_TRANSITION_IDLE. But is is safe when we fix it? It might be as long as we run klp_synchronize_transition() between updating the global @klp_target_state and the other operations. Especially, we need to make sure that @klp_target_state always have either KLP_TRANSITION_PATCHED or KLP_TRANSITION_UNPATCHED when func->transition is set. For this, we would need to add klp_synchronize_transition() into + klp_init_transition() between setting @klp_target_state and func->transition = true. + klp_complete_transition() also for KLP_TRANSITION_UNPATCHED way. It is currently called only for the PATCHED target state. Will this be enough? FACT: It is more complicated than it looked. QUESTION: Is this worth the effort? NOTE: The rcu_read_lock() does not solve the reported stall. We are spending time on it only because it looked nice and simple to you. My opinion: I would personally prefer to focus on solving the stall and the use-after-free access in do_exit(). Best Regards, Petr ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU 2025-02-27 16:22 ` Petr Mladek @ 2025-02-28 2:38 ` Yafang Shao 2025-02-28 6:23 ` Yafang Shao 0 siblings, 1 reply; 9+ messages in thread From: Yafang Shao @ 2025-02-28 2:38 UTC (permalink / raw) To: Petr Mladek; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, live-patching On Fri, Feb 28, 2025 at 12:22 AM Petr Mladek <pmladek@suse.com> wrote: > > On Thu 2025-02-27 10:47:33, Yafang Shao wrote: > > The tasklist_lock in the KLP transition might result high latency under > > specific workload. We can replace it with RCU. > > > > After a new task is forked, its kernel stack is always set to empty[0]. > > Therefore, we can init these new tasks to KLP_TRANSITION_IDLE state > > after they are forked. If these tasks are forked during the KLP > > transition but before klp_check_and_switch_task(), they can be safely > > skipped during klp_check_and_switch_task(). Additionally, if > > klp_ftrace_handler() is triggered right after forking, the task can > > determine which function to use based on the klp_target_state. > > > > With the above change, we can safely convert the tasklist_lock to RCU. > > > > Link: https://lore.kernel.org/all/20250213173253.ovivhuq2c5rmvkhj@jpoimboe/ [0] > > Link: https://lore.kernel.org/all/20250214181206.xkvxohoc4ft26uhf@jpoimboe/ [1] > > Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org> > > Signed-off-by: Yafang Shao <laoar.shao@gmail.com> > > --- > > --- a/kernel/livepatch/patch.c > > +++ b/kernel/livepatch/patch.c > > @@ -95,7 +95,13 @@ static void notrace klp_ftrace_handler(unsigned long ip, > > > > patch_state = current->patch_state; > > > > - WARN_ON_ONCE(patch_state == KLP_TRANSITION_IDLE); > > + /* If the patch_state is KLP_TRANSITION_IDLE, it means the task > > + * was forked after klp_init_transition(). In this case, the > > + * newly forked task can determine which function to use based > > + * on the klp_target_state. > > + */ > > + if (patch_state == KLP_TRANSITION_IDLE) > > + patch_state = klp_target_state; > > > I'm a bit confused by your example. Let me break it down to ensure I understand correctly: > CPU0 CPU1 > > task_0 (forked process): > > funcA > klp_ftrace_handler() > if (patch_state == KLP_TRANSITION_IDLE) > patch_state = klp_target_state > # set to KLP_TRANSITION_PATCHED > > # redirected to klp_funcA() It seems that the KLP is still in the process of transitioning, right? > > > echo 0 >/sys/kernel/livepatch/patch1/enabled > > klp_reverse_transition() > > klp_target_state = !klp_target_state; > # set to KLP_TRANSITION_UNPATCHED If you attempt to initiate a new transition while the current one is still ongoing, the __klp_disable_patch function will return -EBUSY, correct? __klp_disable_patch if (klp_transition_patch) return -EBUSY; On the other hand, if klp_transition_patch is NULL, it indicates that the first KLP transition has completed successfully. > > > funcB > if (patch_state == KLP_TRANSITION_IDLE) > patch_state = klp_target_state > # set to KLP_TRANSITION_UNPATCHED > > # staying in origianl funcB > > > BANG: livepatched and original function called at the same time > > => broken consistency model. > > BTW: This is what I tried to warn you about at > https://lore.kernel.org/r/Z69Wuhve2vnsrtp_@pathway.suse.cz > > See below for more: > > > if (patch_state == KLP_TRANSITION_UNPATCHED) { > > /* > > diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c > > index ba069459c101..af76defca67a 100644 > > --- a/kernel/livepatch/transition.c > > +++ b/kernel/livepatch/transition.c > > @@ -23,7 +23,7 @@ static DEFINE_PER_CPU(unsigned long[MAX_STACK_ENTRIES], klp_stack_entries); > > > > struct klp_patch *klp_transition_patch; > > > > -static int klp_target_state = KLP_TRANSITION_IDLE; > > +int klp_target_state = KLP_TRANSITION_IDLE; > > > > static unsigned int klp_signals_cnt; > > > > @@ -294,6 +294,14 @@ static int klp_check_and_switch_task(struct task_struct *task, void *arg) > > { > > int ret; > > > > + /* > > + * If the patch_state remains KLP_TRANSITION_IDLE at this point, it > > + * indicates that the task was forked after klp_init_transition(). > > + * In this case, it is safe to skip the task. > > + */ > > + if (!test_tsk_thread_flag(task, TIF_PATCH_PENDING)) > > + return 0; > > + > > if (task_curr(task) && task != current) > > return -EBUSY; > > > > @@ -466,11 +474,11 @@ void klp_try_complete_transition(void) > > * Usually this will transition most (or all) of the tasks on a system > > * unless the patch includes changes to a very common function. > > */ > > - read_lock(&tasklist_lock); > > + rcu_read_lock(); > > for_each_process_thread(g, task) > > if (!klp_try_switch_task(task)) > > complete = false; > > - read_unlock(&tasklist_lock); > > + rcu_read_unlock(); > > > > /* > > * Ditto for the idle "swapper" tasks. > > @@ -694,25 +702,10 @@ void klp_reverse_transition(void) > > } > > > > /* Called from copy_process() during fork */ > > -void klp_copy_process(struct task_struct *child) > > +void klp_init_process(struct task_struct *child) > > { > > - > > - /* > > - * The parent process may have gone through a KLP transition since > > - * the thread flag was copied in setup_thread_stack earlier. Bring > > - * the task flag up to date with the parent here. > > - * > > - * The operation is serialized against all klp_*_transition() > > - * operations by the tasklist_lock. The only exceptions are > > - * klp_update_patch_state(current) and __klp_sched_try_switch(), but we > > - * cannot race with them because we are current. > > - */ > > - if (test_tsk_thread_flag(current, TIF_PATCH_PENDING)) > > - set_tsk_thread_flag(child, TIF_PATCH_PENDING); > > - else > > - clear_tsk_thread_flag(child, TIF_PATCH_PENDING); > > - > > - child->patch_state = current->patch_state; > > + clear_tsk_thread_flag(child, TIF_PATCH_PENDING); > > + child->patch_state = KLP_TRANSITION_IDLE; > > I thought that we might do: > > child->patch_state = klp_target_state; > > to avoid the race in the klp_ftrace_handler() described above. > > But the following might happen: > > CPU0 CPU1 > > klp_init_process(child) > > child->patch_state = KLP_TRANSITION_IDLE > > klp_enable_patch() > # setup ftrace handlers > # initialize all processes > # in the task list > > # add "child" into the task list > > schedule() > > [running child now] > > funcA() > > klp_ftrace_handler() > > child->patch_state = KLP_TRANSITION_IDLE > > BANG: Same problem as with the original patch. > > > Hmm, the 2nd version of this patchset tried to do: > > diff --git a/kernel/livepatch/patch.c b/kernel/livepatch/patch.c > index 90408500e5a3..5e523a3fbb3c 100644 > --- a/kernel/livepatch/patch.c > +++ b/kernel/livepatch/patch.c > @@ -95,7 +95,12 @@ static void notrace klp_ftrace_handler(unsigned long ip, > > patch_state = current->patch_state; > > - WARN_ON_ONCE(patch_state == KLP_TRANSITION_IDLE); > + /* If the patch_state is KLP_TRANSITION_IDLE, it indicates the > + * task was forked after klp_init_transition(). For this newly > + * forked task, it is safe to switch it to klp_target_state. > + */ > + if (patch_state == KLP_TRANSITION_IDLE) > + current->patch_state = klp_target_state; > > if (patch_state == KLP_TRANSITION_UNPATCHED) { > /* > > Note: It is broken. It sets current->patch_state but it later > checks the local variable "patch_state" which is still > KLP_TRANSITION_IDLE. You're right—Josh has already highlighted this. I overlooked the local variable. > > But is is safe when we fix it? > > It might be as long as we run klp_synchronize_transition() between > updating the global @klp_target_state and the other operations. > > Especially, we need to make sure that @klp_target_state always have > either KLP_TRANSITION_PATCHED or KLP_TRANSITION_UNPATCHED when > func->transition is set. > > For this, we would need to add klp_synchronize_transition() into > > + klp_init_transition() between setting @klp_target_state > and func->transition = true. > > + klp_complete_transition() also for KLP_TRANSITION_UNPATCHED way. > It is currently called only for the PATCHED target state. > > Will this be enough? > > FACT: It is more complicated than it looked. > QUESTION: Is this worth the effort? > > NOTE: The rcu_read_lock() does not solve the reported stall. > We are spending time on it only because it looked nice and > simple to you. It can help avoid contention on the tasklist_lock. We often encounter latency spikes caused by lock contention, but identifying the root cause isn't always straightforward. If we can eliminate this global lock, I believe it’s a worthwhile improvement. > > My opinion: I would personally prefer to focus on solving > the stall and the use-after-free access in do_exit(). These are distinct issues, and I believe it would be more effective to discuss them separately. -- Regards Yafang ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU 2025-02-28 2:38 ` Yafang Shao @ 2025-02-28 6:23 ` Yafang Shao 0 siblings, 0 replies; 9+ messages in thread From: Yafang Shao @ 2025-02-28 6:23 UTC (permalink / raw) To: Petr Mladek; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, live-patching On Fri, Feb 28, 2025 at 10:38 AM Yafang Shao <laoar.shao@gmail.com> wrote: > > On Fri, Feb 28, 2025 at 12:22 AM Petr Mladek <pmladek@suse.com> wrote: > > > > On Thu 2025-02-27 10:47:33, Yafang Shao wrote: > > > The tasklist_lock in the KLP transition might result high latency under > > > specific workload. We can replace it with RCU. > > > > > > After a new task is forked, its kernel stack is always set to empty[0]. > > > Therefore, we can init these new tasks to KLP_TRANSITION_IDLE state > > > after they are forked. If these tasks are forked during the KLP > > > transition but before klp_check_and_switch_task(), they can be safely > > > skipped during klp_check_and_switch_task(). Additionally, if > > > klp_ftrace_handler() is triggered right after forking, the task can > > > determine which function to use based on the klp_target_state. > > > > > > With the above change, we can safely convert the tasklist_lock to RCU. > > > > > > Link: https://lore.kernel.org/all/20250213173253.ovivhuq2c5rmvkhj@jpoimboe/ [0] > > > Link: https://lore.kernel.org/all/20250214181206.xkvxohoc4ft26uhf@jpoimboe/ [1] > > > Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org> > > > Signed-off-by: Yafang Shao <laoar.shao@gmail.com> > > > --- > > > --- a/kernel/livepatch/patch.c > > > +++ b/kernel/livepatch/patch.c > > > @@ -95,7 +95,13 @@ static void notrace klp_ftrace_handler(unsigned long ip, > > > > > > patch_state = current->patch_state; > > > > > > - WARN_ON_ONCE(patch_state == KLP_TRANSITION_IDLE); > > > + /* If the patch_state is KLP_TRANSITION_IDLE, it means the task > > > + * was forked after klp_init_transition(). In this case, the > > > + * newly forked task can determine which function to use based > > > + * on the klp_target_state. > > > + */ > > > + if (patch_state == KLP_TRANSITION_IDLE) > > > + patch_state = klp_target_state; > > > > > > > I'm a bit confused by your example. Let me break it down to ensure I > understand correctly: > > > CPU0 CPU1 > > > > task_0 (forked process): > > > > funcA > > klp_ftrace_handler() > > if (patch_state == KLP_TRANSITION_IDLE) > > patch_state = klp_target_state > > # set to KLP_TRANSITION_PATCHED > > > > # redirected to klp_funcA() > > It seems that the KLP is still in the process of transitioning, right? > > > > > > > echo 0 >/sys/kernel/livepatch/patch1/enabled > > > > klp_reverse_transition() > > > > klp_target_state = !klp_target_state; > > # set to KLP_TRANSITION_UNPATCHED > > If you attempt to initiate a new transition while the current one is > still ongoing, the __klp_disable_patch function will return -EBUSY, > correct? > > __klp_disable_patch > if (klp_transition_patch) > return -EBUSY; > > On the other hand, if klp_transition_patch is NULL, it indicates that > the first KLP transition has completed successfully. Please disregard my previous comment. I missed the fact that it is called from klp_reverse_transition(). I will review your example again and give it more thought. -- Regards Yafang ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU 2025-02-27 2:47 ` [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU Yafang Shao 2025-02-27 16:22 ` Petr Mladek @ 2025-03-04 14:43 ` Miroslav Benes 1 sibling, 0 replies; 9+ messages in thread From: Miroslav Benes @ 2025-03-04 14:43 UTC (permalink / raw) To: Yafang Shao; +Cc: jpoimboe, jikos, pmladek, joe.lawrence, live-patching fwiw, > + /* > + * If the patch_state remains KLP_TRANSITION_IDLE at this point, it > + * indicates that the task was forked after klp_init_transition(). > + * In this case, it is safe to skip the task. > + */ > + if (!test_tsk_thread_flag(task, TIF_PATCH_PENDING)) > + return 0; if (!klp_patch_pending(task)) Miroslav ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-03-04 15:48 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-02-27 2:47 [PATCH v3 0/2] livepatch: some improvements Yafang Shao 2025-02-27 2:47 ` [PATCH v3 1/2] livepatch: Add comment to clarify klp_add_nops() Yafang Shao 2025-02-27 14:45 ` Petr Mladek 2025-03-04 15:48 ` Petr Mladek 2025-02-27 2:47 ` [PATCH v3 2/2] livepatch: Replace tasklist_lock with RCU Yafang Shao 2025-02-27 16:22 ` Petr Mladek 2025-02-28 2:38 ` Yafang Shao 2025-02-28 6:23 ` Yafang Shao 2025-03-04 14:43 ` Miroslav Benes
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).