Skip to content

Commit d125d13

Browse files
committed
alarmtimer: Prevent starvation by small intervals and SIG_IGN
syzbot reported a RCU stall which is caused by setting up an alarmtimer with a very small interval and ignoring the signal. The reproducer arms the alarm timer with a relative expiry of 8ns and an interval of 9ns. Not a problem per se, but that's an issue when the signal is ignored because then the timer is immediately rearmed because there is no way to delay that rearming to the signal delivery path. See posix_timer_fn() and commit 58229a1 ("posix-timers: Prevent softirq starvation by small intervals and SIG_IGN") for details. The reproducer does not set SIG_IGN explicitely, but it sets up the timers signal with SIGCONT. That has the same effect as explicitely setting SIG_IGN for a signal as SIGCONT is ignored if there is no handler set and the task is not ptraced. The log clearly shows that: [pid 5102] --- SIGCONT {si_signo=SIGCONT, si_code=SI_TIMER, si_timerid=0, si_overrun=316014, si_int=0, si_ptr=NULL} --- It works because the tasks are traced and therefore the signal is queued so the tracer can see it, which delays the restart of the timer to the signal delivery path. But then the tracer is killed: [pid 5087] kill(-5102, SIGKILL <unfinished ...> ... ./strace-static-x86_64: Process 5107 detached and after it's gone the stall can be observed: syzkaller login: [ 79.439102][ C0] hrtimer: interrupt took 68471 ns [ 184.460538][ C1] rcu: INFO: rcu_preempt detected stalls on CPUs/tasks: ... [ 184.658237][ C1] rcu: Stack dump where RCU GP kthread last ran: [ 184.664574][ C1] Sending NMI from CPU 1 to CPUs 0: [ 184.669821][ C0] NMI backtrace for cpu 0 [ 184.669831][ C0] CPU: 0 PID: 5108 Comm: syz-executor192 Not tainted 6.2.0-rc6-next-20230203-syzkaller #0 ... [ 184.670036][ C0] Call Trace: [ 184.670041][ C0] <IRQ> [ 184.670045][ C0] alarmtimer_fired+0x327/0x670 posix_timer_fn() prevents that by checking whether the interval for timers which have the signal ignored is smaller than a jiffie and artifically delay it by shifting the next expiry out by a jiffie. That's accurate vs. the overrun accounting, but slightly inaccurate vs. timer_gettimer(2). The comment in that function says what needs to be done and there was a fix available for the regular userspace induced SIG_IGN mechanism, but that did not work due to the implicit ignore for SIGCONT and similar signals. This needs to be worked on, but for now the only available workaround is to do exactly what posix_timer_fn() does: Increase the interval of self-rearming timers, which have their signal ignored, to at least a jiffie. Interestingly this has been fixed before via commit ff86bf0 ("alarmtimer: Rate limit periodic intervals") already, but that fix got lost in a later rework. Reported-by: [email protected] Fixes: f2c4580 ("alarmtimer: Switch over to generic set/get/rearm routine") Signed-off-by: Thomas Gleixner <[email protected]> Acked-by: John Stultz <[email protected]> Cc: [email protected] Link: https://lore.kernel.org/r/87k00q1no2.ffs@tglx
1 parent ceaa837 commit d125d13

File tree

1 file changed

+29
-4
lines changed

1 file changed

+29
-4
lines changed

kernel/time/alarmtimer.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,35 @@ u64 alarm_forward(struct alarm *alarm, ktime_t now, ktime_t interval)
470470
}
471471
EXPORT_SYMBOL_GPL(alarm_forward);
472472

473-
u64 alarm_forward_now(struct alarm *alarm, ktime_t interval)
473+
static u64 __alarm_forward_now(struct alarm *alarm, ktime_t interval, bool throttle)
474474
{
475475
struct alarm_base *base = &alarm_bases[alarm->type];
476+
ktime_t now = base->get_ktime();
477+
478+
if (IS_ENABLED(CONFIG_HIGH_RES_TIMERS) && throttle) {
479+
/*
480+
* Same issue as with posix_timer_fn(). Timers which are
481+
* periodic but the signal is ignored can starve the system
482+
* with a very small interval. The real fix which was
483+
* promised in the context of posix_timer_fn() never
484+
* materialized, but someone should really work on it.
485+
*
486+
* To prevent DOS fake @now to be 1 jiffie out which keeps
487+
* the overrun accounting correct but creates an
488+
* inconsistency vs. timer_gettime(2).
489+
*/
490+
ktime_t kj = NSEC_PER_SEC / HZ;
491+
492+
if (interval < kj)
493+
now = ktime_add(now, kj);
494+
}
495+
496+
return alarm_forward(alarm, now, interval);
497+
}
476498

477-
return alarm_forward(alarm, base->get_ktime(), interval);
499+
u64 alarm_forward_now(struct alarm *alarm, ktime_t interval)
500+
{
501+
return __alarm_forward_now(alarm, interval, false);
478502
}
479503
EXPORT_SYMBOL_GPL(alarm_forward_now);
480504

@@ -551,9 +575,10 @@ static enum alarmtimer_restart alarm_handle_timer(struct alarm *alarm,
551575
if (posix_timer_event(ptr, si_private) && ptr->it_interval) {
552576
/*
553577
* Handle ignored signals and rearm the timer. This will go
554-
* away once we handle ignored signals proper.
578+
* away once we handle ignored signals proper. Ensure that
579+
* small intervals cannot starve the system.
555580
*/
556-
ptr->it_overrun += alarm_forward_now(alarm, ptr->it_interval);
581+
ptr->it_overrun += __alarm_forward_now(alarm, ptr->it_interval, true);
557582
++ptr->it_requeue_pending;
558583
ptr->it_active = 1;
559584
result = ALARMTIMER_RESTART;

0 commit comments

Comments
 (0)