On Tue, Oct 20 2026 at 15:54, Thomas Gleixner wrote: > Hmm. There is stale state, but there is always _ONE_ CID available > because the last exiting thread drops the per CPU CID. > > As the mode is TRANSIT the whole scenario resolves when the new exec > spawns threads or schedules around. That just works by chance, but I > couldn't find a way to exhaust the bitmap for the initial allocation. > > So I really need to see the reproducer for this splat. Hi Thomas, I have a reproducer for a lockup that starts from the stale state you describe. It needs two more things: the execve() fails, and the task is moved to another CPU during that execve(). The changelog of the patch below has the sequence as a diagram. A failed execve() rejoins the mm it left. The initial allocation succeeds, as you found. But a CID stays set with no owner, so the mm has lost one CID. A single thread should read mm_cid 0. This one reads 1: single thread, mm_cid 1 on CPU 0 after a failed execve moved from CPU 0 to CPU 1 On a guest with two CPUs both bits are now set. The next pthread_create() loops in mm_get_cid(), in sched_mm_cid_fork(). This is on tip sched/urgent (006991aa8997), on guests with two and three CPUs. This lockup is not the splat Jiakai reported. That one loops in the allocation of the first user, in the execve() path. Mine loops at the next clone. I could not show that both come from the same sequence. Your draft cleans up when the first user joins. My fix acts earlier, when the last user leaves. The reproducer is attached: mm_cid_execve_test.c, a selftest for tools/testing/selftests/rseq. Its second case is the sequence of the changelog. Without a fix it fails on guests with two and three CPUs, in six runs out of six. Its first case fails on tip too. That is another problem: after a failed execve() in per CPU mode, the task reads mm_cid 0x80000000. I will post both fixes and the tests as a series. I ran your draft against the reproducer. I applied it by hand on tip plus two other mm_cid fixes of that series, one of them for the 0x80000000 problem. With your draft the second case passes on the same guests, in six runs out of six. So the reset at the first user join also removes the CID without owner. I would like your opinion on the changelog and the comment before I post the series. I found this problem with the help of an LLM coding assistant. Thanks, Mathieu -- >8 -- From: Mathieu Desnoyers Subject: [PATCH] sched/mmcid: Complete the switch to per task mode when the last user leaves sched_mm_cid_exit() ignores the mode change when the last user of an mm leaves. Its comment says there is nothing to do. That holds for an exit: the mm goes away. It holds for a successful execve() too, which joins a new mm. A failed execve() is missed. It leaves the mm in sched_mm_cid_before_execve() and rejoins the same mm in sched_mm_cid_after_execve(). If the last user left in per CPU mode, MM_CID_TRANSIT stays set and the other CPUs still own their CIDs. In the diagram below, the machine has two CPUs. T is the main thread of a process. The four threads it creates only wait. H is another process. It stays on CPU 1. CPU 0 CPU 1 T: creates four threads the mm switches to per CPU mode T: runs on CPU 1 CPU 1 owns a CID T: runs on CPU 0 again CPU 0 owns a CID T: joins the four threads T is the only user left T: execve() sched_mm_cid_before_execve() T leaves as the last user drops the CID of CPU 0 MM_CID_TRANSIT is set CPU 1 still owns its CID H: sched_setaffinity(T, CPU 1) T: migrates to CPU 1 execve() fails sched_mm_cid_after_execve() sched_mm_cid_fork(T) T is the first user mm_get_cid() succeeds writes that CID to the storage of CPU 1 the CID CPU 1 owned keeps its bit, with no owner The mm has one CID fewer from then on. Once every remaining CID is taken, mm_get_cid() loops forever with interrupts disabled. On a guest with two CPUs, the next pthread_create() of T does not return: rcu: INFO: rcu_preempt detected stalls on CPUs/tasks: RIP: 0010:sched_mm_cid_fork+0x1dd/0x660 copy_process+0x2339/0x2ca0 kernel_clone+0xc3/0x3c0 __do_sys_clone3+0xe2/0x130 Per CPU mode lasts down to one user when mm_cid_calc_pcpu_thrs() returns 1. It does with two or three possible CPUs. Fix this by completing the switch when the last user leaves. Run mm_cid_fixup_cpus_to_tasks(), which drops the CIDs the CPUs own and clears MM_CID_TRANSIT. An exit and a successful execve() run the fixup too. They do not need it, but the outcome of an execve() is not known when it leaves the mm. This change was developed with assistance from an LLM. Fixes: 653fda7ae73d ("sched/mmcid: Switch over to the new mechanism") Signed-off-by: Mathieu Desnoyers Link: https://lore.kernel.org/all/87mrs8bhp4.ffs@fw13/ --- kernel/sched/core.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 7e213e88ac28..9ef086f58236 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -11132,13 +11132,21 @@ void sched_mm_cid_exit(struct task_struct *t) return; } /* Last user */ + bool flip; + scoped_guard(raw_spinlock_irq, &mm->mm_cid.lock) { /* Required across execve() */ if (t == current) mm_cid_transit_to_task(t, this_cpu_ptr(mm->mm_cid.pcpu)); - /* Ignore mode change. There is nothing to do. */ - sched_mm_cid_remove_user(t); + flip = sched_mm_cid_remove_user(t); } + /* + * The last user can get the mm back: a failed execve() + * rejoins it. Complete the switch to per task mode, so + * that no CPU keeps a CID. + */ + if (flip) + mm_cid_fixup_cpus_to_tasks(mm); } /* -- Mathieu Desnoyers EfficiOS Inc. https://www.efficios.com