aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2019-02-06 18:51:47 -0500
committerEric W. Biederman <ebiederm@xmission.com>2019-02-07 10:00:36 -0500
commit7146db3317c67b517258cb5e1b08af387da0618b (patch)
tree3db6cf29e1a350fa0e447b6d9320d2898dfb0e5e /kernel
parent35634ffa1751b6efd8cf75010b509dcb0263e29b (diff)
signal: Better detection of synchronous signals
Recently syzkaller was able to create unkillablle processes by creating a timer that is delivered as a thread local signal on SIGHUP, and receiving SIGHUP SA_NODEFERER. Ultimately causing a loop failing to deliver SIGHUP but always trying. When the stack overflows delivery of SIGHUP fails and force_sigsegv is called. Unfortunately because SIGSEGV is numerically higher than SIGHUP next_signal tries again to deliver a SIGHUP. From a quality of implementation standpoint attempting to deliver the timer SIGHUP signal is wrong. We should attempt to deliver the synchronous SIGSEGV signal we just forced. We can make that happening in a fairly straight forward manner by instead of just looking at the signal number we also look at the si_code. In particular for exceptions (aka synchronous signals) the si_code is always greater than 0. That still has the potential to pick up a number of asynchronous signals as in a few cases the same si_codes that are used for synchronous signals are also used for asynchronous signals, and SI_KERNEL is also included in the list of possible si_codes. Still the heuristic is much better and timer signals are definitely excluded. Which is enough to prevent all known ways for someone sending a process signals fast enough to cause unexpected and arguably incorrect behavior. Cc: stable@vger.kernel.org Fixes: a27341cd5fcb ("Prioritize synchronous signals over 'normal' signals") Tested-by: Dmitry Vyukov <dvyukov@google.com> Reported-by: Dmitry Vyukov <dvyukov@google.com> Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/signal.c52
1 files changed, 51 insertions, 1 deletions
diff --git a/kernel/signal.c b/kernel/signal.c
index 5424cb0006bc..99fa8ff06fd9 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -688,6 +688,48 @@ int dequeue_signal(struct task_struct *tsk, sigset_t *mask, kernel_siginfo_t *in
688} 688}
689EXPORT_SYMBOL_GPL(dequeue_signal); 689EXPORT_SYMBOL_GPL(dequeue_signal);
690 690
691static int dequeue_synchronous_signal(kernel_siginfo_t *info)
692{
693 struct task_struct *tsk = current;
694 struct sigpending *pending = &tsk->pending;
695 struct sigqueue *q, *sync = NULL;
696
697 /*
698 * Might a synchronous signal be in the queue?
699 */
700 if (!((pending->signal.sig[0] & ~tsk->blocked.sig[0]) & SYNCHRONOUS_MASK))
701 return 0;
702
703 /*
704 * Return the first synchronous signal in the queue.
705 */
706 list_for_each_entry(q, &pending->list, list) {
707 /* Synchronous signals have a postive si_code */
708 if ((q->info.si_code > SI_USER) &&
709 (sigmask(q->info.si_signo) & SYNCHRONOUS_MASK)) {
710 sync = q;
711 goto next;
712 }
713 }
714 return 0;
715next:
716 /*
717 * Check if there is another siginfo for the same signal.
718 */
719 list_for_each_entry_continue(q, &pending->list, list) {
720 if (q->info.si_signo == sync->info.si_signo)
721 goto still_pending;
722 }
723
724 sigdelset(&pending->signal, sync->info.si_signo);
725 recalc_sigpending();
726still_pending:
727 list_del_init(&sync->list);
728 copy_siginfo(info, &sync->info);
729 __sigqueue_free(sync);
730 return info->si_signo;
731}
732
691/* 733/*
692 * Tell a process that it has a new active signal.. 734 * Tell a process that it has a new active signal..
693 * 735 *
@@ -2411,7 +2453,15 @@ relock:
2411 goto relock; 2453 goto relock;
2412 } 2454 }
2413 2455
2414 signr = dequeue_signal(current, &current->blocked, &ksig->info); 2456 /*
2457 * Signals generated by the execution of an instruction
2458 * need to be delivered before any other pending signals
2459 * so that the instruction pointer in the signal stack
2460 * frame points to the faulting instruction.
2461 */
2462 signr = dequeue_synchronous_signal(&ksig->info);
2463 if (!signr)
2464 signr = dequeue_signal(current, &current->blocked, &ksig->info);
2415 2465
2416 if (!signr) 2466 if (!signr)
2417 break; /* will return 0 */ 2467 break; /* will return 0 */