aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiroslav Benes <mbenes@suse.cz>2015-04-09 17:28:45 +0200
committerJiri Slaby <jslaby@suse.cz>2017-03-10 19:51:14 +0100
commit6cbd8925bf80f38cc060edc5d8cc95beaf341a89 (patch)
treeb96a336fd0e3110c3886d8de8722f98d72029fa0
parente71c481d90a3141c55b98c4197bdf3baa9863553 (diff)
downloadlinux-klp.tar.gz
livepatch: send a fake signal to all tasksklp
kGraft consistency model is of LEAVE_KERNEL 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 place is on the boundary between kernel and userspace. The patching waits for all tasks to cross this boundary and finishes the process afterwards. The problem is that a task can block the finalization of patching process for quite a long time, if not forever. The task could sleep somewhere in the kernel or could be running in the userspace with no prospect of entering the kernel and thus going through the safe place. Luckily we can force the task to do that 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. Also 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. Last, 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. 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_64.S on syscall and interrupt/exception exit paths. Signed-off-by: Miroslav Benes <mbenes@suse.cz> Reviewed-by: Jiri Kosina <jkosina@suse.cz> Cc: Ingo Molnar <mingo@redhat.com> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Peter Zijlstra <peterz@infradead.org> Signed-off-by: Jiri Slaby <jslaby@suse.cz>
-rw-r--r--kernel/livepatch/cmodel-kgraft.c22
-rw-r--r--kernel/signal.c3
2 files changed, 24 insertions, 1 deletions
diff --git a/kernel/livepatch/cmodel-kgraft.c b/kernel/livepatch/cmodel-kgraft.c
index ea3f88106304a2..b3f830ac3aaf29 100644
--- a/kernel/livepatch/cmodel-kgraft.c
+++ b/kernel/livepatch/cmodel-kgraft.c
@@ -108,6 +108,26 @@ unlock:
return failed;
}
+static void klp_kgraft_send_fake_signal(void)
+{
+ struct task_struct *p, *t;
+
+ read_lock(&tasklist_lock);
+ for_each_process_thread(p, t) {
+ /*
+ * send fake signal to all non-kthread processes which are still
+ * not migrated
+ */
+ if ((t->flags & PF_KTHREAD) || !klp_kgraft_task_in_progress(t))
+ continue;
+
+ spin_lock_irq(&t->sighand->siglock);
+ signal_wake_up(t, 0);
+ spin_unlock_irq(&t->sighand->siglock);
+ }
+ read_unlock(&tasklist_lock);
+}
+
static void klp_kgraft_work_fn(struct work_struct *work)
{
static bool printed = false;
@@ -118,6 +138,8 @@ static void klp_kgraft_work_fn(struct work_struct *work)
KGRAFT_TIMEOUT);
printed = true;
}
+ /* send fake signal */
+ klp_kgraft_send_fake_signal();
/* recheck again later */
queue_delayed_work(klp_kgraft_wq, &klp_kgraft_work,
KGRAFT_TIMEOUT * HZ);
diff --git a/kernel/signal.c b/kernel/signal.c
index d51c5ddd855c84..5a3f56a691224e 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -157,7 +157,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_kgraft_task_in_progress(current))
clear_thread_flag(TIF_SIGPENDING);
}