Skip to content

[BUG REPORT] Potential deadlock on SignalFdInode.state #1820

@CatlessFish

Description

@CatlessFish

signalfdSIGPROF timer hardirq 下可能发生同锁重入死锁

问题摘要

SignalFdInode.state 当前使用普通 SpinLock<SignalFdState>,多个进程上下文路径通过 self.state.lock() 获取该锁,而 lock() 不关闭本地硬中断。

例如SignalFdInode::read_at中:

let state_guard = self.state.lock();
if self.nonblock(&state_guard) {
return Err(SystemError::EAGAIN_OR_EWOULDBLOCK);
}
drop(state_guard);

当线程正在执行 signalfd 相关操作并持有这把锁时,如果本 CPU 的 timer hardirq 恰好到来,且 ITIMER_PROF 到期触发 SIGPROF,内核会在 hardirq 主链路中沿着以下调用链执行:

  • tick_handle_periodic
  • update_process_times
  • irqtime_account_process_tick
  • send_signal_to_pcb(SIGPROF)
  • complete_signal
  • notify_signalfd_for_pcb
  • SignalFdInode::notify_signal

notify_signal() 会再次获取同一个 self.state.lock(),从而形成“进程上下文持普通自旋锁,被本 CPU hardirq 重入同锁”的自锁死。

pub fn notify_signal(&self, sig: Signal) {
let mask = self.state.lock().mask;
if !mask.contains(sig.into()) {
return;
}
// 唤醒阻塞 read() 的等待者
self.wait_queue.wakeup_all(None);
// 唤醒 epoll 等待者
let _ = EventPoll::wakeup_epoll(
&self.epitems,
EPollEventType::EPOLLIN | EPollEventType::EPOLLRDNORM,
);
}

影响范围

理论上,线程只要满足以下条件:

  1. signalfd 的 mask 包含 SIGPROF
  2. 线程或进程启用了 ITIMER_PROF
  3. 线程以非irq_save的方式获取SignalFdInode.state.lock() (例如,执行read_at()readable()

就可能以 timer tick 在 SignalFdInode.state.lock() 的普通持锁窗口内到来这一时机触发自锁死。

一个可行的PoC是在监听 SIGPROFsignalfd、已配置 ITIMER_PROF的条件下阻塞 read(signalfd) 循环,因为它会高频进入 read_at() 的空队列路径。

修复方案

由于SignalFdInode.state这个锁可能会在 hardirq 上下文获取,因此所有其余地方对该锁的获取应从self.state.lock()改为self.state.lock_irq_save()以确保持锁时关闭中断。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug-report这是一个bug报告(如果确认是一个bug,请管理人员添加`bug` label)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions