Skip to content

Commit 22b886d

Browse files
htejunKAGA-KOKO
authored andcommitted
timers: Use proper base migration in add_timer_on()
Regardless of the previous CPU a timer was on, add_timer_on() currently simply sets timer->flags to the new CPU. As the caller must be seeing the timer as idle, this is locally fine, but the timer leaving the old base while unlocked can lead to race conditions as follows. Let's say timer was on cpu 0. cpu 0 cpu 1 ----------------------------------------------------------------------------- del_timer(timer) succeeds del_timer(timer) lock_timer_base(timer) locks cpu_0_base add_timer_on(timer, 1) spin_lock(&cpu_1_base->lock) timer->flags set to cpu_1_base operates on @Timer operates on @Timer This triggered with mod_delayed_work_on() which contains "if (del_timer()) add_timer_on()" sequence eventually leading to the following oops. BUG: unable to handle kernel NULL pointer dereference at (null) IP: [<ffffffff810ca6e9>] detach_if_pending+0x69/0x1a0 ... Workqueue: wqthrash wqthrash_workfunc [wqthrash] task: ffff8800172ca680 ti: ffff8800172d0000 task.ti: ffff8800172d0000 RIP: 0010:[<ffffffff810ca6e9>] [<ffffffff810ca6e9>] detach_if_pending+0x69/0x1a0 ... Call Trace: [<ffffffff810cb0b4>] del_timer+0x44/0x60 [<ffffffff8106e836>] try_to_grab_pending+0xb6/0x160 [<ffffffff8106e913>] mod_delayed_work_on+0x33/0x80 [<ffffffffa0000081>] wqthrash_workfunc+0x61/0x90 [wqthrash] [<ffffffff8106dba8>] process_one_work+0x1e8/0x650 [<ffffffff8106e05e>] worker_thread+0x4e/0x450 [<ffffffff810746af>] kthread+0xef/0x110 [<ffffffff8185980f>] ret_from_fork+0x3f/0x70 Fix it by updating add_timer_on() to perform proper migration as __mod_timer() does. Reported-and-tested-by: Jeff Layton <jlayton@poochiereds.net> Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Chris Worley <chris.worley@primarydata.com> Cc: bfields@fieldses.org Cc: Michael Skralivetsky <michael.skralivetsky@primarydata.com> Cc: Trond Myklebust <trond.myklebust@primarydata.com> Cc: Shaohua Li <shli@fb.com> Cc: Jeff Layton <jlayton@poochiereds.net> Cc: kernel-team@fb.com Cc: stable@vger.kernel.org Link: http://lkml.kernel.org/r/20151029103113.2f893924@tlielax.poochiereds.net Link: http://lkml.kernel.org/r/20151104171533.GI5749@mtj.duckdns.org Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
1 parent 66ef349 commit 22b886d

File tree

1 file changed

+19
-3
lines changed

1 file changed

+19
-3
lines changed

kernel/time/timer.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -977,13 +977,29 @@ EXPORT_SYMBOL(add_timer);
977977
*/
978978
void add_timer_on(struct timer_list *timer, int cpu)
979979
{
980-
struct tvec_base *base = per_cpu_ptr(&tvec_bases, cpu);
980+
struct tvec_base *new_base = per_cpu_ptr(&tvec_bases, cpu);
981+
struct tvec_base *base;
981982
unsigned long flags;
982983

983984
timer_stats_timer_set_start_info(timer);
984985
BUG_ON(timer_pending(timer) || !timer->function);
985-
spin_lock_irqsave(&base->lock, flags);
986-
timer->flags = (timer->flags & ~TIMER_BASEMASK) | cpu;
986+
987+
/*
988+
* If @timer was on a different CPU, it should be migrated with the
989+
* old base locked to prevent other operations proceeding with the
990+
* wrong base locked. See lock_timer_base().
991+
*/
992+
base = lock_timer_base(timer, &flags);
993+
if (base != new_base) {
994+
timer->flags |= TIMER_MIGRATING;
995+
996+
spin_unlock(&base->lock);
997+
base = new_base;
998+
spin_lock(&base->lock);
999+
WRITE_ONCE(timer->flags,
1000+
(timer->flags & ~TIMER_BASEMASK) | cpu);
1001+
}
1002+
9871003
debug_activate(timer, timer->expires);
9881004
internal_add_timer(base, timer);
9891005
spin_unlock_irqrestore(&base->lock, flags);

0 commit comments

Comments
 (0)