Skip to content

Commit bc7b3e9

Browse files
ebiedermgregkh
authored andcommitted
signal: Only reschedule timers on signals timers have sent
commit 57db7e4a2d92c2d3dfbca4ef8057849b2682436b upstream. Thomas Gleixner wrote: > The CRIU support added a 'feature' which allows a user space task to send > arbitrary (kernel) signals to itself. The changelog says: > > The kernel prevents sending of siginfo with positive si_code, because > these codes are reserved for kernel. I think we can allow a task to > send such a siginfo to itself. This operation should not be dangerous. > > Quite contrary to that claim, it turns out that it is outright dangerous > for signals with info->si_code == SI_TIMER. The following code sequence in > a user space task allows to crash the kernel: > > id = timer_create(CLOCK_XXX, ..... signo = SIGX); > timer_set(id, ....); > info->si_signo = SIGX; > info->si_code = SI_TIMER: > info->_sifields._timer._tid = id; > info->_sifields._timer._sys_private = 2; > rt_[tg]sigqueueinfo(..., SIGX, info); > sigemptyset(&sigset); > sigaddset(&sigset, SIGX); > rt_sigtimedwait(sigset, info); > > For timers based on CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID this > results in a kernel crash because sigwait() dequeues the signal and the > dequeue code observes: > > info->si_code == SI_TIMER && info->_sifields._timer._sys_private != 0 > > which triggers the following callchain: > > do_schedule_next_timer() -> posix_cpu_timer_schedule() -> arm_timer() > > arm_timer() executes a list_add() on the timer, which is already armed via > the timer_set() syscall. That's a double list add which corrupts the posix > cpu timer list. As a consequence the kernel crashes on the next operation > touching the posix cpu timer list. > > Posix clocks which are internally implemented based on hrtimers are not > affected by this because hrtimer_start() can handle already armed timers > nicely, but it's a reliable way to trigger the WARN_ON() in > hrtimer_forward(), which complains about calling that function on an > already armed timer. This problem has existed since the posix timer code was merged into 2.5.63. A few releases earlier in 2.5.60 ptrace gained the ability to inject not just a signal (which linux has supported since 1.0) but the full siginfo of a signal. The core problem is that the code will reschedule in response to signals getting dequeued not just for signals the timers sent but for other signals that happen to a si_code of SI_TIMER. Avoid this confusion by testing to see if the queued signal was preallocated as all timer signals are preallocated, and so far only the timer code preallocates signals. Move the check for if a timer needs to be rescheduled up into collect_signal where the preallocation check must be performed, and pass the result back to dequeue_signal where the code reschedules timers. This makes it clear why the code cares about preallocated timers. Reported-by: Thomas Gleixner <tglx@linutronix.de> History Tree: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git Reference: 66dd34a ("signal: allow to send any siginfo to itself") Reference: 1669ce53e2ff ("Add PTRACE_GETSIGINFO and PTRACE_SETSIGINFO") Fixes: db8b50ba75f2 ("[PATCH] POSIX clocks & timers") Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 005253f commit bc7b3e9

1 file changed

Lines changed: 14 additions & 6 deletions

File tree

kernel/signal.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,8 @@ int unhandled_signal(struct task_struct *tsk, int sig)
503503
return !tsk->ptrace;
504504
}
505505

506-
static void collect_signal(int sig, struct sigpending *list, siginfo_t *info)
506+
static void collect_signal(int sig, struct sigpending *list, siginfo_t *info,
507+
bool *resched_timer)
507508
{
508509
struct sigqueue *q, *first = NULL;
509510

@@ -525,6 +526,12 @@ static void collect_signal(int sig, struct sigpending *list, siginfo_t *info)
525526
still_pending:
526527
list_del_init(&first->list);
527528
copy_siginfo(info, &first->info);
529+
530+
*resched_timer =
531+
(first->flags & SIGQUEUE_PREALLOC) &&
532+
(info->si_code == SI_TIMER) &&
533+
(info->si_sys_private);
534+
528535
__sigqueue_free(first);
529536
} else {
530537
/*
@@ -541,12 +548,12 @@ static void collect_signal(int sig, struct sigpending *list, siginfo_t *info)
541548
}
542549

543550
static int __dequeue_signal(struct sigpending *pending, sigset_t *mask,
544-
siginfo_t *info)
551+
siginfo_t *info, bool *resched_timer)
545552
{
546553
int sig = next_signal(pending, mask);
547554

548555
if (sig)
549-
collect_signal(sig, pending, info);
556+
collect_signal(sig, pending, info, resched_timer);
550557
return sig;
551558
}
552559

@@ -558,15 +565,16 @@ static int __dequeue_signal(struct sigpending *pending, sigset_t *mask,
558565
*/
559566
int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
560567
{
568+
bool resched_timer = false;
561569
int signr;
562570

563571
/* We only dequeue private signals from ourselves, we don't let
564572
* signalfd steal them
565573
*/
566-
signr = __dequeue_signal(&tsk->pending, mask, info);
574+
signr = __dequeue_signal(&tsk->pending, mask, info, &resched_timer);
567575
if (!signr) {
568576
signr = __dequeue_signal(&tsk->signal->shared_pending,
569-
mask, info);
577+
mask, info, &resched_timer);
570578
/*
571579
* itimer signal ?
572580
*
@@ -611,7 +619,7 @@ int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
611619
*/
612620
current->jobctl |= JOBCTL_STOP_DEQUEUED;
613621
}
614-
if ((info->si_code & __SI_MASK) == __SI_TIMER && info->si_sys_private) {
622+
if (resched_timer) {
615623
/*
616624
* Release the siglock to ensure proper locking order
617625
* of timer locks outside of siglocks. Note, we leave

0 commit comments

Comments
 (0)