From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934341AbdERMBS (ORCPT ); Thu, 18 May 2017 08:01:18 -0400 Received: from mx2.suse.de ([195.135.220.15]:56533 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S934108AbdERMBN (ORCPT ); Thu, 18 May 2017 08:01:13 -0400 From: Miroslav Benes To: jpoimboe@redhat.com, jeyu@redhat.com, jikos@kernel.org Cc: pmladek@suse.com, live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, Miroslav Benes , Oleg Nesterov Subject: [PATCH 2/3] livepatch: send a fake signal to all blocking tasks Date: Thu, 18 May 2017 14:00:42 +0200 Message-Id: <20170518120043.7205-3-mbenes@suse.cz> X-Mailer: git-send-email 2.12.2 In-Reply-To: <20170518120043.7205-1-mbenes@suse.cz> References: <20170518120043.7205-1-mbenes@suse.cz> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 in a different way. 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 entry.S 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. 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 force sysfs attribute in livepatch sysfs directory. Cc: Oleg Nesterov Signed-off-by: Miroslav Benes --- include/linux/livepatch.h | 3 +++ kernel/livepatch/core.c | 3 +++ kernel/livepatch/transition.c | 40 ++++++++++++++++++++++++++++++++++++++++ kernel/livepatch/transition.h | 1 + kernel/signal.c | 4 +++- 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 194991ef9347..43cfeebeb42b 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -29,6 +29,9 @@ #include +/* values for sysfs force attribute */ +#define KLP_FORCE_FAKE 1 + /* task patch states */ #define KLP_UNDEFINED -1 #define KLP_UNPATCHED 0 diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 84f8944704ad..bb3b78fa7d2b 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -466,6 +466,9 @@ static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr, } switch (val) { + case KLP_FORCE_FAKE: + klp_send_fake_signal(); + break; default: return -EINVAL; } diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c index adc0cc64aa4b..bb61aaa196d3 100644 --- a/kernel/livepatch/transition.c +++ b/kernel/livepatch/transition.c @@ -551,3 +551,43 @@ void klp_copy_process(struct task_struct *child) /* TIF_PATCH_PENDING gets copied in setup_thread_stack() */ } + +/* + * Sends a fake signal to all non-kthread tasks with TIF_PATCH_PENDING set. + * Kthreads with TIF_PATCH_PENDING set are woken up. Only admin can request this + * action currently. + */ +void klp_send_fake_signal(void) +{ + struct task_struct *g, *task; + + pr_info("sending a fake signal and waking sleeping kthreads up\n"); + + read_lock(&tasklist_lock); + for_each_process_thread(g, task) { + if (!klp_patch_pending(task)) + continue; + + /* + * There is a small race here. We could see TIF_PATCH_PENDING + * set and decide to wake up a kthread or send a fake signal. + * Meanwhile the task could migrate itself and the action + * would be meaningless. It is not serious though. + */ + if (task->flags & PF_KTHREAD) { + /* + * Wake up a kthread which still has not been migrated. + */ + wake_up_process(task); + } else { + /* + * Send fake signal to all non-kthread tasks which are + * still not migrated. + */ + spin_lock_irq(&task->sighand->siglock); + signal_wake_up(task, 0); + spin_unlock_irq(&task->sighand->siglock); + } + } + read_unlock(&tasklist_lock); +} diff --git a/kernel/livepatch/transition.h b/kernel/livepatch/transition.h index ce09b326546c..1c7ede6eaa77 100644 --- a/kernel/livepatch/transition.h +++ b/kernel/livepatch/transition.h @@ -10,5 +10,6 @@ void klp_cancel_transition(void); void klp_start_transition(void); void klp_try_complete_transition(void); void klp_reverse_transition(void); +void klp_send_fake_signal(void); #endif /* _LIVEPATCH_TRANSITION_H */ diff --git a/kernel/signal.c b/kernel/signal.c index 7e59ebc2c25e..3a25cc06231d 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -39,6 +39,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -162,7 +163,8 @@ void recalc_sigpending_and_wake(struct task_struct *t) void recalc_sigpending(void) { - if (!recalc_sigpending_tsk(current) && !freezing(current)) + if (!recalc_sigpending_tsk(current) && !freezing(current) && + !klp_patch_pending(current)) clear_thread_flag(TIF_SIGPENDING); } -- 2.12.2