aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/livepatch
diff options
context:
space:
mode:
authorMiroslav Benes <mbenes@suse.cz>2017-11-15 08:50:13 -0500
committerJiri Kosina <jkosina@suse.cz>2017-12-04 16:34:57 -0500
commit43347d56c8d9dd732cee2f8efd384ad21dd1f6c4 (patch)
treed736327e4f0c339dba9449cd22c35917ab93ee5d /kernel/livepatch
parent0ef76878cfcf4d6b64972b283021f576a95d9216 (diff)
livepatch: send a fake signal to all blocking tasks
Live patching consistency model is of LEAVE_PATCHED_SET and SWITCH_THREAD. This means that all tasks in the system have to be marked one by one as safe to call a new patched function. Safe means when a task is not (sleeping) in a set of patched functions. That is, no patched function is on the task's stack. Another clearly safe place is the boundary between kernel and userspace. The patching waits for all tasks to get outside of the patched set or to cross the boundary. The transition is completed afterwards. The problem is that a task can block the transition for quite a long time, if not forever. It could sleep in a set of patched functions, for example. Luckily we can force the task to leave the set by sending it a fake signal, that is a signal with no data in signal pending structures (no handler, no sign of proper signal delivered). Suspend/freezer use this to freeze the tasks as well. The task gets TIF_SIGPENDING set and is woken up (if it has been sleeping in the kernel before) or kicked by rescheduling IPI (if it was running on other CPU). This causes the task to go to kernel/userspace boundary where the signal would be handled and the task would be marked as safe in terms of live patching. There are tasks which are not affected by this technique though. The fake signal is not sent to kthreads. They should be handled differently. They can be woken up so they leave the patched set and their TIF_PATCH_PENDING can be cleared thanks to stack checking. For the sake of completeness, if the task is in TASK_RUNNING state but not currently running on some CPU it doesn't get the IPI, but it would eventually handle the signal anyway. Second, if the task runs in the kernel (in TASK_RUNNING state) it gets the IPI, but the signal is not handled on return from the interrupt. It would be handled on return to the userspace in the future when the fake signal is sent again. Stack checking deals with these cases in a better way. If the task was sleeping in a syscall it would be woken by our fake signal, it would check if TIF_SIGPENDING is set (by calling signal_pending() predicate) and return ERESTART* or EINTR. Syscalls with ERESTART* return values are restarted in case of the fake signal (see do_signal()). EINTR is propagated back to the userspace program. This could disturb the program, but... * each process dealing with signals should react accordingly to EINTR return values. * syscalls returning EINTR happen to be quite common situation in the system even if no fake signal is sent. * freezer sends the fake signal and does not deal with EINTR anyhow. Thus EINTR values are returned when the system is resumed. The very safe marking is done in architectures' "entry" on syscall and interrupt/exception exit paths, and in a stack checking functions of livepatch. TIF_PATCH_PENDING is cleared and the next recalc_sigpending() drops TIF_SIGPENDING. In connection with this, also call klp_update_patch_state() before do_signal(), so that recalc_sigpending() in dequeue_signal() can clear TIF_PATCH_PENDING immediately and thus prevent a double call of do_signal(). Note that the fake signal is not sent to stopped/traced tasks. Such task prevents the patching to finish till it continues again (is not traced anymore). Last, sending the fake signal is not automatic. It is done only when admin requests it by writing 1 to signal sysfs attribute in livepatch sysfs directory. Signed-off-by: Miroslav Benes <mbenes@suse.cz> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Ingo Molnar <mingo@redhat.com> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: linuxppc-dev@lists.ozlabs.org Cc: x86@kernel.org Acked-by: Michael Ellerman <mpe@ellerman.id.au> (powerpc) Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'kernel/livepatch')
-rw-r--r--kernel/livepatch/core.c30
-rw-r--r--kernel/livepatch/transition.c41
-rw-r--r--kernel/livepatch/transition.h1
3 files changed, 72 insertions, 0 deletions
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index de9e45dca70f..88766bd91803 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -454,6 +454,7 @@ EXPORT_SYMBOL_GPL(klp_enable_patch);
454 * /sys/kernel/livepatch/<patch> 454 * /sys/kernel/livepatch/<patch>
455 * /sys/kernel/livepatch/<patch>/enabled 455 * /sys/kernel/livepatch/<patch>/enabled
456 * /sys/kernel/livepatch/<patch>/transition 456 * /sys/kernel/livepatch/<patch>/transition
457 * /sys/kernel/livepatch/<patch>/signal
457 * /sys/kernel/livepatch/<patch>/<object> 458 * /sys/kernel/livepatch/<patch>/<object>
458 * /sys/kernel/livepatch/<patch>/<object>/<function,sympos> 459 * /sys/kernel/livepatch/<patch>/<object>/<function,sympos>
459 */ 460 */
@@ -528,11 +529,40 @@ static ssize_t transition_show(struct kobject *kobj,
528 patch == klp_transition_patch); 529 patch == klp_transition_patch);
529} 530}
530 531
532static ssize_t signal_store(struct kobject *kobj, struct kobj_attribute *attr,
533 const char *buf, size_t count)
534{
535 struct klp_patch *patch;
536 int ret;
537 bool val;
538
539 patch = container_of(kobj, struct klp_patch, kobj);
540
541 /*
542 * klp_mutex lock is not grabbed here intentionally. It is not really
543 * needed. The race window is harmless and grabbing the lock would only
544 * hold the action back.
545 */
546 if (patch != klp_transition_patch)
547 return -EINVAL;
548
549 ret = kstrtobool(buf, &val);
550 if (ret)
551 return ret;
552
553 if (val)
554 klp_send_signals();
555
556 return count;
557}
558
531static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled); 559static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled);
532static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition); 560static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition);
561static struct kobj_attribute signal_kobj_attr = __ATTR_WO(signal);
533static struct attribute *klp_patch_attrs[] = { 562static struct attribute *klp_patch_attrs[] = {
534 &enabled_kobj_attr.attr, 563 &enabled_kobj_attr.attr,
535 &transition_kobj_attr.attr, 564 &transition_kobj_attr.attr,
565 &signal_kobj_attr.attr,
536 NULL 566 NULL
537}; 567};
538 568
diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
index 56add6327736..edcfcb8ebb2d 100644
--- a/kernel/livepatch/transition.c
+++ b/kernel/livepatch/transition.c
@@ -608,3 +608,44 @@ void klp_copy_process(struct task_struct *child)
608 608
609 /* TIF_PATCH_PENDING gets copied in setup_thread_stack() */ 609 /* TIF_PATCH_PENDING gets copied in setup_thread_stack() */
610} 610}
611
612/*
613 * Sends a fake signal to all non-kthread tasks with TIF_PATCH_PENDING set.
614 * Kthreads with TIF_PATCH_PENDING set are woken up. Only admin can request this
615 * action currently.
616 */
617void klp_send_signals(void)
618{
619 struct task_struct *g, *task;
620
621 pr_notice("signaling remaining tasks\n");
622
623 read_lock(&tasklist_lock);
624 for_each_process_thread(g, task) {
625 if (!klp_patch_pending(task))
626 continue;
627
628 /*
629 * There is a small race here. We could see TIF_PATCH_PENDING
630 * set and decide to wake up a kthread or send a fake signal.
631 * Meanwhile the task could migrate itself and the action
632 * would be meaningless. It is not serious though.
633 */
634 if (task->flags & PF_KTHREAD) {
635 /*
636 * Wake up a kthread which sleeps interruptedly and
637 * still has not been migrated.
638 */
639 wake_up_state(task, TASK_INTERRUPTIBLE);
640 } else {
641 /*
642 * Send fake signal to all non-kthread tasks which are
643 * still not migrated.
644 */
645 spin_lock_irq(&task->sighand->siglock);
646 signal_wake_up(task, 0);
647 spin_unlock_irq(&task->sighand->siglock);
648 }
649 }
650 read_unlock(&tasklist_lock);
651}
diff --git a/kernel/livepatch/transition.h b/kernel/livepatch/transition.h
index 0f6e27c481f9..40522795a5f6 100644
--- a/kernel/livepatch/transition.h
+++ b/kernel/livepatch/transition.h
@@ -11,5 +11,6 @@ void klp_cancel_transition(void);
11void klp_start_transition(void); 11void klp_start_transition(void);
12void klp_try_complete_transition(void); 12void klp_try_complete_transition(void);
13void klp_reverse_transition(void); 13void klp_reverse_transition(void);
14void klp_send_signals(void);
14 15
15#endif /* _LIVEPATCH_TRANSITION_H */ 16#endif /* _LIVEPATCH_TRANSITION_H */