All of lore.kernel.org
 help / color / mirror / Atom feed
From: Josh Poimboeuf <jpoimboe@redhat.com>
To: Petr Mladek <pmladek@suse.com>
Cc: Jessica Yu <jeyu@redhat.com>, Jiri Kosina <jikos@kernel.org>,
	Miroslav Benes <mbenes@suse.cz>,
	linux-kernel@vger.kernel.org, live-patching@vger.kernel.org,
	Michael Ellerman <mpe@ellerman.id.au>,
	Heiko Carstens <heiko.carstens@de.ibm.com>,
	x86@kernel.org, linuxppc-dev@lists.ozlabs.org,
	linux-s390@vger.kernel.org, Vojtech Pavlik <vojtech@suse.com>,
	Jiri Slaby <jslaby@suse.cz>,
	Chris J Arges <chris.j.arges@canonical.com>,
	Andy Lutomirski <luto@kernel.org>, Ingo Molnar <mingo@kernel.org>,
	Peter Zijlstra <peterz@infradead.org>,
	Kamalesh Babulal <kamalesh@linux.vnet.ibm.com>,
	Balbir Singh <bsingharora@gmail.com>
Subject: Re: [PATCH v4 13/15] livepatch: change to a per-task consistency model
Date: Mon, 6 Feb 2017 13:51:48 -0600	[thread overview]
Message-ID: <20170206195148.c75y3ru54s425f7k@treble> (raw)
In-Reply-To: <20170206164431.GA2980@pathway.suse.cz>

On Mon, Feb 06, 2017 at 05:44:31PM +0100, Petr Mladek wrote:
> > > > @@ -347,22 +354,37 @@ static int __klp_enable_patch(struct klp_patch *patch)
> > > >  
> > > >  	pr_notice("enabling patch '%s'\n", patch->mod->name);
> > > >  
> > > > +	klp_init_transition(patch, KLP_PATCHED);
> > > > +
> > > > +	/*
> > > > +	 * Enforce the order of the func->transition writes in
> > > > +	 * klp_init_transition() and the ops->func_stack writes in
> > > > +	 * klp_patch_object(), so that klp_ftrace_handler() will see the
> > > > +	 * func->transition updates before the handler is registered and the
> > > > +	 * new funcs become visible to the handler.
> > > > +	 */
> > > > +	smp_wmb();
> > > > +
> > > >  	klp_for_each_object(patch, obj) {
> > > >  		if (!klp_is_object_loaded(obj))
> > > >  			continue;
> > > >  
> > > >  		ret = klp_patch_object(obj);
> > > > -		if (ret)
> > > > -			goto unregister;
> > > > +		if (ret) {
> > > > +			pr_warn("failed to enable patch '%s'\n",
> > > > +				patch->mod->name);
> > > > +
> > > > +			klp_unpatch_objects(patch);
> > > 
> > > We should call here synchronize_rcu() here as we do
> > > in klp_try_complete_transition(). Some of the affected
> > > functions might have more versions on the stack and we
> > > need to make sure that klp_ftrace_handler() will _not_
> > > see the removed patch on the stack.
> > 
> > Even if the handler sees the new func on the stack, the
> > task->patch_state is still KLP_UNPATCHED, so it will still choose the
> > previous version of the function.  Or did I miss your point?
> 
> The barrier is needed from exactly the same reason as the one
> in klp_try_complete_transition()
> 
> CPU0					CPU1
> 
> __klp_enable_patch()
>   klp_init_transition()
> 
>     for_each...
>       task->patch_state = KLP_UNPATCHED
> 
>     for_each...
>       func->transition = true
> 
>   klp_for_each_object()
>     klp_patch_object()
>       list_add_rcu()
> 
> 					klp_ftrace_handler()
> 					  func = list_first_...()
> 
> 					  if (func->transition)
> 
> 
>     ret = klp_patch_object()
>     /* error */
>     if (ret) {
>       klp_unpatch_objects()
> 
> 	list_remove_rcu()
> 
>       klp_complete_transition()
> 
> 	for_each_....
> 	  func->transition = true
> 
> 	for_each_....
> 	  task->patch_state = PATCH_UNDEFINED
> 
> 					    patch_state = current->patch_state;
> 					    WARN_ON_ONCE(patch_state
> 							==
> 							 KLP_UNDEFINED);
> 
> BANG: The warning is triggered.
> 
> => we need to call rcu_synchronize(). It will make sure that
> no ftrace handled will see the removed func on the stack
> and we could clear all the other values.

Makes sense.

Notice in this case that klp_target_state is KLP_PATCHED.  Which means
that klp_complete_transition() would not call synchronize_rcu() at the
right time, nor would it call module_put().  It can be fixed with:

@@ -387,7 +389,7 @@ static int __klp_enable_patch(struct klp_patch *patch)
 			pr_warn("failed to enable patch '%s'\n",
 				patch->mod->name);
 
-			klp_unpatch_objects(patch);
+			klp_target_state = KLP_UNPATCHED;
 			klp_complete_transition();
 
 			return ret;

This assumes that the 'if (klp_target_state == KLP_UNPATCHED)' clause in
klp_try_complete_transition() gets moved to klp_complete_transition() as
you suggested.

> > > > diff --git a/kernel/livepatch/patch.c b/kernel/livepatch/patch.c
> > > > index 5efa262..1a77f05 100644
> > > > --- a/kernel/livepatch/patch.c
> > > > +++ b/kernel/livepatch/patch.c
> > > > @@ -29,6 +29,7 @@
> > > >  #include <linux/bug.h>
> > > >  #include <linux/printk.h>
> > > >  #include "patch.h"
> > > > +#include "transition.h"
> > > >  
> > > >  static LIST_HEAD(klp_ops);
> > > >  
> > > > @@ -54,15 +55,58 @@ static void notrace klp_ftrace_handler(unsigned long ip,
> > > >  {
> > > >  	struct klp_ops *ops;
> > > >  	struct klp_func *func;
> > > > +	int patch_state;
> > > >  
> > > >  	ops = container_of(fops, struct klp_ops, fops);
> > > >  
> > > >  	rcu_read_lock();
> > > > +
> > > >  	func = list_first_or_null_rcu(&ops->func_stack, struct klp_func,
> > > >  				      stack_node);
> > > > +
> > > > +	/*
> > > > +	 * func should never be NULL because preemption should be disabled here
> > > > +	 * and unregister_ftrace_function() does the equivalent of a
> > > > +	 * synchronize_sched() before the func_stack removal.
> > > > +	 */
> > > > +	if (WARN_ON_ONCE(!func))
> > > > +		goto unlock;
> > > > +
> > > > +	/*
> > > > +	 * Enforce the order of the ops->func_stack and func->transition reads.
> > > > +	 * The corresponding write barrier is in __klp_enable_patch().
> > > > +	 */
> > > > +	smp_rmb();
> > > 
> > > I was curious why the comment did not mention __klp_disable_patch().
> > > It was related to the hours of thinking. I would like to avoid this
> > > in the future and add a comment like.
> > > 
> > > 	 * This barrier probably is not needed when the patch is being
> > > 	 * disabled. The patch is removed from the stack in
> > > 	 * klp_try_complete_transition() and there we need to call
> > > 	 * rcu_synchronize() to prevent seeing the patch on the stack
> > > 	 * at all.
> > > 	 *
> > > 	 * Well, it still might be needed to see func->transition
> > > 	 * when the patch is removed and the task is migrated. See
> > > 	 * the write barrier in __klp_disable_patch().
> > 
> > Agreed, though as you mentioned earlier, there's also the implicit
> > barrier in klp_update_patch_state(), which would execute first in such a
> > scenario.  So I think I'll update the barrier comments in
> > klp_update_patch_state().
> 
> You inspired me to a scenario with 3 CPUs:
> 
> CPU0			CPU1			CPU2
> 
> __klp_disable_patch()
> 
>   klp_init_transition()
> 
>     func->transition = true
> 
>   smp_wmb()
> 
>   klp_start_transition()
> 
>     set TIF_PATCH_PATCHPENDING
> 
> 			klp_update_patch_state()
> 
> 			  task->patch_state
> 			     = KLP_UNPATCHED
> 
> 			  smp_mb()
> 
> 						klp_ftrace_handler()
> 						  func = list_...
> 
> 						  smp_rmb() /*needed?*/
> 
> 						  if (func->transition)
> 

I think this isn't possible.  Remember the comment I added to
klp_update_patch_state():

 * NOTE: If task is not 'current', the caller must ensure the task is inactive.
 * Otherwise klp_ftrace_handler() might read the wrong 'patch_state' value.

Right now klp_update_patch_state() is only called for current.
klp_ftrace_handler() on CPU2 would be running in the context of a
different task.

> We need to make sure the CPU3 sees func->transition set. Otherwise,
> it would wrongly use the function from the patch.
> 
> So, the description might be:
> 
> 	 * Enforce the order of the ops->func_stack and
> 	 * func->transition reads when the patch is enabled.
> 	 * The corresponding write barrier is in __klp_enable_patch().
> 	 *
> 	 * Also make sure that func->transition is visible before
> 	 * TIF_PATCH_PENDING_FLAG is set and the task might get
> 	 * migrated to KLP_UNPATCHED state. The corresponding
> 	 * write barrier is in __klp_disable_patch().
> 
> 
> By other words, the read barrier here is needed from the same
> reason as the write barrier in __klp_disable_patch().
> > > > +void klp_reverse_transition(void)
> > > > +{
> > > > +	unsigned int cpu;
> > > > +	struct task_struct *g, *task;
> > > > +
> > > > +	klp_transition_patch->enabled = !klp_transition_patch->enabled;
> > > > +
> > > > +	klp_target_state = !klp_target_state;
> > > > +
> > > > +	/*
> > > > +	 * Clear all TIF_PATCH_PENDING flags to prevent races caused by
> > > > +	 * klp_update_patch_state() running in parallel with
> > > > +	 * klp_start_transition().
> > > > +	 */
> > > > +	read_lock(&tasklist_lock);
> > > > +	for_each_process_thread(g, task)
> > > > +		clear_tsk_thread_flag(task, TIF_PATCH_PENDING);
> > > > +	read_unlock(&tasklist_lock);
> > > > +
> > > > +	for_each_possible_cpu(cpu)
> > > > +		clear_tsk_thread_flag(idle_task(cpu), TIF_PATCH_PENDING);
> > > > +
> > > > +	/* Let any remaining calls to klp_update_patch_state() complete */
> > > > +	synchronize_rcu();
> > > > +
> > > > +	klp_start_transition();
> > > 
> > > Hmm, we should not call klp_try_complete_transition() when
> > > klp_start_transition() is called from here. I can't find a safe
> > > way to cancel klp_transition_work() when we own klp_mutex.
> > > It smells with a possible deadlock.
> > > 
> > > I suggest to move move klp_try_complete_transition() outside
> > > klp_start_transition() and explicitely call it from
> > >  __klp_disable_patch() and __klp_enabled_patch().
> > > This would fix also the problem with immediate patches, see
> > > klp_start_transition().
> > 
> > Agreed.  I'll fix it as you suggest and I'll put the mod_delayed_work()
> > call in klp_reverse_transition() again.
> 
> There is one small catch. The mod_delayed_work() might cause that two
> works might be scheduled:
> 
>   + one already running that is waiting for the klp_mutex
>   + another one scheduled by that mod_delayed_work()
>
> A solution would be to cancel the work from klp_transition_work_fn()
> if the transition succeeds.
> 
> Another possibility would be to do nothing here. The work is
> scheduled very often anyway.

Yes, I think I'll do this, for the sake of simplicity.

-- 
Josh

  reply	other threads:[~2017-02-06 19:51 UTC|newest]

Thread overview: 44+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-01-19 15:46 [PATCH v4 00/15] livepatch: hybrid consistency model Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 01/15] stacktrace/x86: add function for detecting reliable stack traces Josh Poimboeuf
2017-01-26 13:56   ` Petr Mladek
2017-01-26 17:57     ` Josh Poimboeuf
2017-01-27  8:47   ` Miroslav Benes
2017-01-27 17:13     ` Josh Poimboeuf
2017-02-01 19:57   ` [PATCH v4.1 " Josh Poimboeuf
2017-02-02 14:39     ` Miroslav Benes
2017-01-19 15:46 ` [PATCH v4 02/15] x86/entry: define _TIF_ALLWORK_MASK flags explicitly Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 03/15] livepatch: create temporary klp_update_patch_state() stub Josh Poimboeuf
2017-01-27  8:52   ` Miroslav Benes
2017-01-19 15:46 ` [PATCH v4 04/15] livepatch/x86: add TIF_PATCH_PENDING thread flag Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 05/15] livepatch/powerpc: " Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 06/15] livepatch/s390: reorganize TIF thread flag bits Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 07/15] livepatch/s390: add TIF_PATCH_PENDING thread flag Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 08/15] livepatch: separate enabled and patched states Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 09/15] livepatch: remove unnecessary object loaded check Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 10/15] livepatch: move patching functions into patch.c Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 11/15] livepatch: use kstrtobool() in enabled_store() Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 12/15] livepatch: store function sizes Josh Poimboeuf
2017-01-19 15:46 ` [PATCH v4 13/15] livepatch: change to a per-task consistency model Josh Poimboeuf
2017-02-02 11:45   ` Petr Mladek
2017-02-02 11:47     ` Petr Mladek
2017-02-02 11:51   ` Petr Mladek
2017-02-03 16:21     ` Miroslav Benes
2017-02-03 20:39     ` Josh Poimboeuf
2017-02-06 16:44       ` Petr Mladek
2017-02-06 19:51         ` Josh Poimboeuf [this message]
2017-02-08 15:47           ` Petr Mladek
2017-02-08 16:46             ` Josh Poimboeuf
2017-02-09 10:24               ` Petr Mladek
2017-02-03 16:41   ` Miroslav Benes
2017-02-06 15:58     ` Josh Poimboeuf
2017-02-07  8:21       ` Miroslav Benes
2017-01-19 15:46 ` [PATCH v4 14/15] livepatch: add /proc/<pid>/patch_state Josh Poimboeuf
2017-01-31 14:31   ` Miroslav Benes
2017-01-31 14:56     ` Josh Poimboeuf
2017-02-01  8:54       ` Miroslav Benes
2017-01-19 15:46 ` [PATCH v4 15/15] livepatch: allow removal of a disabled patch Josh Poimboeuf
2017-02-03 16:48   ` Miroslav Benes
2017-02-01 20:02 ` [PATCH v4 00/15] livepatch: hybrid consistency model Josh Poimboeuf
2017-02-01 20:52   ` Miroslav Benes
2017-02-01 21:01   ` Jiri Kosina
2017-02-02 14:37   ` Petr Mladek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170206195148.c75y3ru54s425f7k@treble \
    --to=jpoimboe@redhat.com \
    --cc=bsingharora@gmail.com \
    --cc=chris.j.arges@canonical.com \
    --cc=heiko.carstens@de.ibm.com \
    --cc=jeyu@redhat.com \
    --cc=jikos@kernel.org \
    --cc=jslaby@suse.cz \
    --cc=kamalesh@linux.vnet.ibm.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-s390@vger.kernel.org \
    --cc=linuxppc-dev@lists.ozlabs.org \
    --cc=live-patching@vger.kernel.org \
    --cc=luto@kernel.org \
    --cc=mbenes@suse.cz \
    --cc=mingo@kernel.org \
    --cc=mpe@ellerman.id.au \
    --cc=peterz@infradead.org \
    --cc=pmladek@suse.com \
    --cc=vojtech@suse.com \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.