Re: [PATCH v3 13/15] livepatch: change to a per-task consistency model

Petr Mladek
2017-01-10T13:00:58Z

On Thu 2016-12-22 12:31:37, Josh Poimboeuf wrote:
> On Thu, Dec 22, 2016 at 03:34:52PM +0100, Petr Mladek wrote:
> > On Wed 2016-12-21 15:25:05, Josh Poimboeuf wrote:
> > > On Tue, Dec 20, 2016 at 06:32:46PM +0100, Petr Mladek wrote:
> > > > On Thu 2016-12-08 12:08:38, Josh Poimboeuf wrote:
> > > > > Change livepatch to use a basic per-task consistency model. This is the
> > > > > foundation which will eventually enable us to patch those ~10% of
> > > > > security patches which change function or data semantics. This is the
> > > > > biggest remaining piece needed to make livepatch more generally useful.
> > > > >
> > > > > [1] https://lkml.kernel.org/r/20141107140458.GA21774@suse.cz
> > > > >
> > > > > --- /dev/null
> > > > > +++ b/kernel/livepatch/transition.c
> > > > > + /*
> > > > > + * Enforce the order of the task->patch_state initializations and the
> > > > > + * func->transition updates to ensure that, in the enable path,
> > > > > + * klp_ftrace_handler() doesn't see a func in transition with a
> > > > > + * task->patch_state of KLP_UNDEFINED.
> > > > > + */
> > > > > + smp_wmb();
> > > > > +
> > > > > + /*
> > > > > + * Set the func transition states so klp_ftrace_handler() will know to
> > > > > + * switch to the transition logic.
> > > > > + *
> > > > > + * When patching, the funcs aren't yet in the func_stack and will be
> > > > > + * made visible to the ftrace handler shortly by the calls to
> > > > > + * klp_patch_object().
> > > > > + *
> > > > > + * When unpatching, the funcs are already in the func_stack and so are
> > > > > + * already visible to the ftrace handler.
> > > > > + */
> > > > > + klp_for_each_object(patch, obj)
> > > > > + klp_for_each_func(obj, func)
> > > > > + func->transition = true;
> > > > > +}
> > > > > +
> > > > > +/*
> > > > > + * Start the transition to the specified target patch state so tasks can begin
> > > > > + * switching to it.
> > > > > + */
> > > > > +void klp_start_transition(void)
> > > > > +{
> > > > > + struct task_struct *g, *task;
> > > > > + unsigned int cpu;
> > > > > +
> > > > > + WARN_ON_ONCE(klp_target_state == KLP_UNDEFINED);
> > > > > +
> > > > > + pr_notice("'%s': %s...\n", klp_transition_patch->mod->name,
> > > > > + klp_target_state == KLP_PATCHED ? "patching" : "unpatching");
> > > > > +
> > > > > + /*
> > > > > + * If the patch can be applied or reverted immediately, skip the
> > > > > + * per-task transitions.
> > > > > + */
> > > > > + if (klp_transition_patch->immediate)
> > > > > + return;
> > > > > +
> > > > > + /*
> > > > > + * Mark all normal tasks as needing a patch state update. As they pass
> > > > > + * through the syscall barrier they'll switch over to the target state
> > > > > + * (unless we switch them in klp_try_complete_transition() first).
> > > > > + */
> > > > > + read_lock(&tasklist_lock);
> > > > > + for_each_process_thread(g, task)
> > > > > + set_tsk_thread_flag(task, TIF_PATCH_PENDING);
> > > >
> > > > This is called also from klp_reverse_transition(). We should set it
> > > > only when the task need migration. Also we should clear it when
> > > > the task is in the right state already.
> > > >
> > > > It is not only optimization. It actually solves a race between
> > > > klp_complete_transition() and klp_update_patch_state(), see below.
> > >
> > > I agree about the race, but if I did:
> > >
> > > for_each_process_thread(g, task) {
> > > if (task->patch_state != klp_target_state)
> > > set_tsk_thread_flag(task, TIF_PATCH_PENDING);
> > > else
> > > clear_tsk_thread_flag(task, TIF_PATCH_PENDING);
> > > }
> > >
> > > It would still leave a small window where TIF_PATCH_PENDING gets set for
> > > an already patched task, if klp_update_patch_state() is running at the
> > > same time.
> >
> > I see your point. Well, it seems that it is more complicated:
> >
> > The race would be possible only when this was called from
> > klp_reverse_transition(). But we need to call there
> > rcu_synchronize() to prevent races with klp_update_patch_state()
> > also to prevent prelimitary patch completion.
> >
> > The result is:
> >
> > if (task->patch_state != klp_target_state) {
> > # it means that the task was already migrated before
> > # we reverted klp_target_state. It means that
> > # the TIF flag was already cleared, the related
> > # klp_update_patch_state() already finished (thanks
> > # to rcu_synchronize() and new one will be called
> > # only when we set the flag again
> > # => it is safe to set it
> >
> > # we should also check and warn when the TIF flag
> > # was not clear before we set it here
> >
> >
> > else
> >
> > # the task was not migrated before we reverted
> > # klp_target_state. klp_update_patch_state()
> > # could run in parallel but it will do the same
> > # what we do, clear TIF flag and keep the patch_state
> > # as is
> > # => it is safe to clear it
> >
> >
> > I agree that this is complex like hell. But it also allows use to
> > check that things work as we expect.
>
> Ouch. I agree that it seems safe but it's way too hard to reason about.
> And then it gets worse if you try to think about what happens when
> adding another reverse operation.
>
> >
> > If we always set the flag here and always clear it later, we might
> > hide a bug.
> >
> > If we want to make it slightly more straightforward, we might
> > clear TIF flags in klp_reverse_transaction() before we revert
> > klp_target_state. The later rcu_synchronize() should make sure
> > that all migrations are finished and non-will run in parallel.
> > Then we could set the TIF flag only where needed here.
>
> I think this last paragraph is important. It would simplify things
> greatly and ensure we won't have klp_update_patch_state() changing
> things in the background.

OK, let's clear all TIF_PATCH_PENDIG flags and call rcu_synchronize()
at the beginning of klp_reverse_transition(). It might be slightly
suboptimal but it greatly simplifies the situation. I vote for it.
We need to prevent our heads from cracking ;-)

Note that I would still set TIF_PATCH_PENDING flag only for tasks
that are not in the requested state.

> > > > > + read_unlock(&tasklist_lock);
> > > > > +
> > > > > + /*
> > > > > + * Ditto for the idle "swapper" tasks, though they never cross the
> > > > > + * syscall barrier. Instead they switch over in cpu_idle_loop().
> > > > > + */
> > > > > + get_online_cpus();
> > > > > + for_each_online_cpu(cpu)
> > > > > + set_tsk_thread_flag(idle_task(cpu), TIF_PATCH_PENDING);
> > > > > + put_online_cpus();
> > > >
> > > > Also this stage need to be somehow handled by CPU coming/going
> > > > handlers.
> > >
> > > Here I think we could automatically switch any offline CPUs' idle tasks.
> > > And something similar in klp_try_complete_transition().
> >
> > We still need to make sure to do not race with the cpu_up()/cpu_down()
> > calls.
>
> Hm, maybe we'd need to call cpu_hotplug_disable() before switching the
> offline idle tasks?
>
> > I would use here the trick with for_each_possible_cpu() and let
> > the migration for the stack check.
>
> There are a few issues with that:
>
> 1) The idle task of a missing CPU doesn't *have* a stack, so it doesn't
> make much sense to try to check it.
>
> 2) We can't rely *only* on the stack check, because not all arches have
> it. The other way to migrate idle tasks is from the idle loop switch
> point. But if the task's CPU is down, its idle loop isn't running so
> it can't migrate.
>
> (Note this is currently a theoretical point: we currently don't allow
> such arches to use the consistency model anyway because there's no
> way for them to migrate kthreads.)

Good points. My only concern is that the transaction might take a long
or even forever. I am not sure if it is wise to disable cpu_hotplug
for the entire transaction.

A compromise might be to disable cpu hotplug only when the task
state is manipulated a more complex way. Hmm, cpu_hotplug_disable()
looks like a rather costly function. We should not call it in
klp_try_complete_transition(). But we could do:

1. When the patch is being enabled, disable cpu hotplug,
go through each_possible_cpu and setup the transaction
only for CPUs that are online. Then we could enable
the hotplug again.

2. Check only each_online_cpu in klp_try_complete_transition().
If all tasks are migrated, disable cpu hotplug and re-check
idle tasks on online CPUs. If any is not migrated, enable
hotplug and return failure. Othewise, continue with
completion of the transaction. [*]

3. In klp_complete_transition, update all tasks including
the offline CPUs and enable cpu hotplug again.

If the re-check in the 2nd step looks ugly, we could add some hotlug
notifiers to make sure that enabled/disabled CPUs are in a reasonable
state. We still should disable the hotplug in the 1st and 3rd step.

BTW: There is a new API for the cpu hotplug callbacks. I was involved
in one conversion. You might take inspiration in
drivers/thermal/intel_powerclamp.c. See cpuhp_setup_state_nocalls()
there.

[...]

> > > > > diff --git a/samples/livepatch/livepatch-sample.c b/samples/livepatch/livepatch-sample.c
> > > > > index e34f871..bb61c65 100644
> > > > > --- a/samples/livepatch/livepatch-sample.c
> > > > > +++ b/samples/livepatch/livepatch-sample.c
> > > > > @@ -17,6 +17,8 @@
> > > > > * along with this program; if not, see <http://www.gnu.org/licenses/>.
> > > > > */
> > > > >
> > > > > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > > > > +
> > > > > #include <linux/module.h>
> > > > > #include <linux/kernel.h>
> > > > > #include <linux/livepatch.h>
> > > > > @@ -69,6 +71,11 @@ static int livepatch_init(void)
> > > > > {
> > > > > int ret;
> > > > >
> > > > > + if (!klp_have_reliable_stack() && !patch.immediate) {
> > > > > + pr_notice("disabling consistency model!\n");
> > > > > + patch.immediate = true;
> > > > > + }
> > > >
> > > > I am scared to have this in the sample module. It makes sense
> > > > to use the consistency model even for immediate patches because
> > > > it allows to remove them. But this must not be used for patches
> > > > that really require the consistency model. We should add
> > > > a big fat warning at least.
> > >
> > > I did this so that the sample module would still work for non-x86_64
> > > arches, for which there's currently no way to patch kthreads.
> > >
> > > Notice I did add a warning:
> > >
> > > pr_notice("disabling consistency model!\n");
> > >
> > > Is the warning not fat enough?
> >
> > The warning does not explain who did it, why, if it is safe, and when
> > this could be used. I suggest a comment like:
> >
> > /*
> > * WARNING: Use this check only when you know what you do!
> > *
> > * This sample patch does not change the semantic of the data structures,
> > * locks, or return adresses. It is safe to be applied immediately.
> > * But we want to test and use the consistency model on supported
> > * architectures. It allows to remove the patch module.
> > *
> > * See Documentation/livepatch/livepatch.txt for more details, please.
> > */
> >
> > Also the message might be more explicit.
> >
> > pr_notice("livepatch-sample: The consistency model is not supported on
> > this architecture. Using the immediate model that is safe enough.\n");
>
> Ok, will try to do something like that.
>
> > Alternatively, we might allow more values for patch.immediate, e.g.
> >
> > enum klp_consistency_model {
> > KLP_CM_IMMEDIATE,
> > KLP_CM_TASK,
> > KLP_CM_TASK_OR_IMMEDIATE,
> > };
> >
> > Then we could do the decision on the kernel side.
> > But I am not sure if this would be widely used and it
> > it is worth the complication.
>
> I'd rather avoid that :-)

Same here :-)

Best Regards,
Petr