Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

softirq: Fix suspicious RCU usage in __do_softirq()

Currently, the condition "__this_cpu_read(ksoftirqd) == current" is used to
invoke rcu_softirq_qs() in ksoftirqd tasks context for non-RT kernels.

This works correctly as long as the context is actually task context but
this condition is wrong when:

- the current task is ksoftirqd
- the task is interrupted in a RCU read side critical section
- __do_softirq() is invoked on return from interrupt

Syzkaller triggered the following scenario:

-> finish_task_switch()
-> put_task_struct_rcu_user()
-> call_rcu(&task->rcu, delayed_put_task_struct)
-> __kasan_record_aux_stack()
-> pfn_valid()
-> rcu_read_lock_sched()
<interrupt>
__irq_exit_rcu()
-> __do_softirq)()
-> if (!IS_ENABLED(CONFIG_PREEMPT_RT) &&
__this_cpu_read(ksoftirqd) == current)
-> rcu_softirq_qs()
-> RCU_LOCKDEP_WARN(lock_is_held(&rcu_sched_lock_map))

The rcu quiescent state is reported in the rcu-read critical section, so
the lockdep warning is triggered.

Fix this by splitting out the inner working of __do_softirq() into a helper
function which takes an argument to distinguish between ksoftirqd task
context and interrupted context and invoke it from the relevant call sites
with the proper context information and use that for the conditional
invocation of rcu_softirq_qs().

Reported-by: syzbot+dce04ed6d1438ad69656@syzkaller.appspotmail.com
Suggested-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Zqiang <qiang.zhang1211@gmail.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20240427102808.29356-1-qiang.zhang1211@gmail.com
Link: https://lore.kernel.org/lkml/8f281a10-b85a-4586-9586-5bbc12dc784f@paulmck-laptop/T/#mea8aba4abfcb97bbf499d169ce7f30c4cff1b0e3

authored by

Zqiang and committed by
Thomas Gleixner
1dd1eff1 c26591af

+8 -4
+8 -4
kernel/softirq.c
··· 508 508 static inline void lockdep_softirq_end(bool in_hardirq) { } 509 509 #endif 510 510 511 - asmlinkage __visible void __softirq_entry __do_softirq(void) 511 + static void handle_softirqs(bool ksirqd) 512 512 { 513 513 unsigned long end = jiffies + MAX_SOFTIRQ_TIME; 514 514 unsigned long old_flags = current->flags; ··· 563 563 pending >>= softirq_bit; 564 564 } 565 565 566 - if (!IS_ENABLED(CONFIG_PREEMPT_RT) && 567 - __this_cpu_read(ksoftirqd) == current) 566 + if (!IS_ENABLED(CONFIG_PREEMPT_RT) && ksirqd) 568 567 rcu_softirq_qs(); 569 568 570 569 local_irq_disable(); ··· 581 582 lockdep_softirq_end(in_hardirq); 582 583 softirq_handle_end(); 583 584 current_restore_flags(old_flags, PF_MEMALLOC); 585 + } 586 + 587 + asmlinkage __visible void __softirq_entry __do_softirq(void) 588 + { 589 + handle_softirqs(false); 584 590 } 585 591 586 592 /** ··· 925 921 * We can safely run softirq on inline stack, as we are not deep 926 922 * in the task stack here. 927 923 */ 928 - __do_softirq(); 924 + handle_softirqs(true); 929 925 ksoftirqd_run_end(); 930 926 cond_resched(); 931 927 return;