sched discussion
[PATCH] futex: Fix might_sleep() warning in futex_pivot_pending()
LLM 分析
futex:futex_pivot_pending() 中修复 might_sleep() 警告
系列概况
- 标题: [PATCH] futex: Fix might_sleep() warning in futex_pivot_pending()
- 作者: Yao Kai yaokai34@huawei.com(由 syzbot 转交,AI 协助起草 Gemini 3.6-flash / 3.1-pro-preview)
- 版本: v1(单封补丁)
- 规模: 仅修改 1 个文件,5 处 hunk
- 修改文件:
kernel/futex/core.c - 代码统计: 净增约 10 行(移除 1 行
guard(mutex),新增 RCU 读侧 + smp_rmb() + WRITE_ONCE 包装) - Message-ID: 515ea00f-a081-4b9a-bcb3-f5517fd4e565@mail.kernel.org
- 完整性: 补丁完整,含
Fixes: 8e7ff730dd96、Assisted-by、Reported-bysyzbot、Signed-off-by、base-commit
补丁目的
消除 syzbot 报告的 might_sleep() 警告。
前一个 commit 8e7ff730dd96("futex: Fix race in futex_pivot_pending() during private hash resize")把 futex_pivot_pending() 改成持 mmph->lock 来消除 race。但 futex_pivot_pending() 在 futex_hash_allocate() 内作为 wait_var_event() 的条件 callback 调用,而 wait_var_event() 在求值条件前会把 task 状态置为 TASK_UNINTERRUPTIBLE,此时再调 mutex_lock() 违反 sleeping 规则:
WARNING: kernel/sched/core.c:9124 at __might_sleep+0x92/0xf0
Call Trace:
__mutex_lock_common kernel/locking/mutex.c:623
futex_pivot_pending kernel/futex/core.c:1789
futex_hash_allocate+0x7fb/0xf00 kernel/futex/core.c:1872
__do_sys_prctl kernel/sys.c:2885
旧流程的问题
futex_pivot_pending() 在 hash resize 期间需要判断私有 hash 是否已被替换成新 hash。前一版 fix 用 mmph->mutex 保护,但 mutex 在 waitqueue 条件 callback 内调用是非法的,且任何 sleeping 调用都不允许在 !TASK_RUNNING 状态下发生。
新流程
回退到无锁实现:进入 RCU 读侧,先 rcu_dereference(hash)、再 smp_rmb()、再 READ_ONCE(hash_new),借助 Message Passing (MP) pattern 保证正确性而不阻塞:
guard(rcu)();
fph = rcu_dereference(mmph->hash);
/*
* 若看到新 hash,则一定看到 hash_new 被清零。
* 与 __futex_pivot_hash() 中的 rcu_assign_pointer() 配对。
*/
smp_rmb();
if (!READ_ONCE(mmph->hash_new))
return true;
return futex_ref_is_dead(fph);
写侧 __futex_pivot_hash() 通过 rcu_assign_pointer() 提供 release 屏障,reader 端 smp_rmb() 配对,保证 MP 关系。mm->futex.phash.hash_new 在所有写点都用 WRITE_ONCE() 包装,防止编译器把 store 优化掉。
关键实现
- RCU 生命周期:旧 hash 通过
kvfree_rcu()释放,reader 处于 RCU 读侧临界区时旧 hash 内存一定有效。 - 屏障配对:
smp_rmb()与写侧rcu_assign_pointer()的隐含 release barrier 严格配对,缺一会失效。 - write 侧 WRITE_ONCE:
__futex_pivot_hash()、futex_pivot_hash()、futex_hash_allocate()多个写点统一改为WRITE_ONCE(hash_new, ...)。 - lost-wakeup 修补:Peter Zijlstra 在 review 中指出
add_wait_queue()与wake_up_var()之间仍存在窗口,需要在add_wait_queue之后再补一个smp_mb(),最终采纳smp_mb__after_spinlock()。 while化简:MAX_SCHEDULE_TIMEOUT永不返回 0,可把wait_woken()返回值检查从while条件中移除。
类比
把 hash pivot 想象成「搬家换门牌号」。读者拿着旧门牌去敲门,必须先确认公告牌确实换了。rcu_dereference(hash) 是「看公告牌当前挂着哪一块」;smp_rmb() 是「保证我看到新牌子时,旧牌子一定已经被撕掉,绝不会看到新牌子挂着、旧公告还在」的视觉确认;旧房子通过 kvfree_rcu() 延迟拆除,住客在 RCU 临界区内绝不会被扫地出门。
Highlight:风险与注意点
- Lost-wakeup 窗口:仅靠
add_wait_queue()内部的 UNLOCK 不够,PowerPC 上的 RCtso 弱序需要补smp_mb__after_spinlock()。 - 屏障对称性:单独在 reader 一侧加
smp_rmb()而不与rcu_assign_pointer()release 配对,整套推理立即失效。 - kvfree_rcu 依赖:reader 安全完全建立在
kvfree_rcu()上;若将来切换为同步kfree()释放,立即 UAF。 - wait_woken 优化:
MAX_SCHEDULE_TIMEOUT路径下返回值总非 0,可去掉多余的&& wait_woken(...)检查。 - AI 自动起草风险:当前 patch 由 Gemini 自动生成、人类作者签字,需要更多真实 workload 验证 RCU 读侧开销。
版本变化
- v1:单封补丁,把
futex_pivot_pending()改回 RCU + smp_rmb() 无锁实现,对应Fixes: 8e7ff730dd96。 - review 阶段:Peter 指出 lost-wakeup 风险并建议加
smp_mb();Yao 改用smp_mb__after_spinlock(),并在最后两封邮件中由 Peter 接受这一选择。讨论尚未发布 v2,正式结论以新补丁为准。
一句话总结
把 futex_pivot_pending() 从持锁版本回退到 RCU + smp_rmb() 无锁实现消掉 might_sleep(),再补一道 smp_mb__after_spinlock() 关上 wait_var_event 内部的 lost-wakeup 窗口。
pivot 完成判定(reader / writer 同步)
T1 (waiter via prctl) T2 (hasher / ref-put path)
---------------------------------- ------------------------------------
add_wait_queue(wq_head, &entry) futex_ref_put() (atomic full mb)
STORE wq_entry wake_up_var()
UNLOCK wq_head->lock └ lockless waitqueue_active() check
smp_mb__after_spinlock() rcu_assign_pointer(hash,new) [RELEASE]
↕ pairs with futex_ref_put mb WRITE_ONCE(hash_new, NULL)
guard(rcu)() (old hash released via kvfree_rcu)
rcu_dereference(hash) ---+
smp_rmb() | → 看到 hash==new ⇒ hash_new 必为 NULL
READ_ONCE(hash_new) ----+ ⇒ return true(pivot 完成,退出 wait)
→ 否则 futex_ref_is_dead(old)?
true ⇒ return true
false ⇒ wait_woken(... MAX_SCHEDULE_TIMEOUT)