Commit 1e83ccd5921a for kernel

commit 1e83ccd5921a610ef409a7d4e56db27822b4ea39
Author: Thomas Gleixner <tglx@kernel.org>
Date:   Tue Feb 10 17:20:51 2026 +0100

    sched/mmcid: Don't assume CID is CPU owned on mode switch

    Shinichiro reported a KASAN UAF, which is actually an out of bounds access
    in the MMCID management code.

       CPU0                                         CPU1
                                                    T1 runs in userspace
       T0: fork(T4) -> Switch to per CPU CID mode
             fixup() set MM_CID_TRANSIT on T1/CPU1
       T4 exit()
       T3 exit()
       T2 exit()
                                                    T1 exit() switch to per task mode
                                                     ---> Out of bounds access.

    As T1 has not scheduled after T0 set the TRANSIT bit, it exits with the
    TRANSIT bit set. sched_mm_cid_remove_user() clears the TRANSIT bit in
    the task and drops the CID, but it does not touch the per CPU storage.
    That's functionally correct because a CID is only owned by the CPU when
    the ONCPU bit is set, which is mutually exclusive with the TRANSIT flag.

    Now sched_mm_cid_exit() assumes that the CID is CPU owned because the
    prior mode was per CPU. It invokes mm_drop_cid_on_cpu() which clears the
    not set ONCPU bit and then invokes clear_bit() with an insanely large
    bit number because TRANSIT is set (bit 29).

    Prevent that by actually validating that the CID is CPU owned in
    mm_drop_cid_on_cpu().

    Fixes: 007d84287c74 ("sched/mmcid: Drop per CPU CID immediately when switching to per task mode")
    Reported-by: Shinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
    Signed-off-by: Thomas Gleixner <tglx@kernel.org>
    Tested-by: Shinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
    Cc: stable@vger.kernel.org
    Closes: https://lore.kernel.org/aYsZrixn9b6s_2zL@shinmob
    Reviewed-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
    Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 7c8b769c0d0d..759777694c78 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -10788,10 +10788,9 @@ void sched_mm_cid_exit(struct task_struct *t)
 					return;
 				/*
 				 * Mode change. The task has the CID unset
-				 * already. The CPU CID is still valid and
-				 * does not have MM_CID_TRANSIT set as the
-				 * mode change has just taken effect under
-				 * mm::mm_cid::lock. Drop it.
+				 * already and dealt with an eventually set
+				 * TRANSIT bit. If the CID is owned by the CPU
+				 * then drop it.
 				 */
 				mm_drop_cid_on_cpu(mm, this_cpu_ptr(mm->mm_cid.pcpu));
 			}
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e51bfa3586fa..b82fb70a9d54 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -3813,8 +3813,10 @@ static __always_inline void mm_unset_cid_on_task(struct task_struct *t)
 static __always_inline void mm_drop_cid_on_cpu(struct mm_struct *mm, struct mm_cid_pcpu *pcp)
 {
 	/* Clear the ONCPU bit, but do not set UNSET in the per CPU storage */
-	pcp->cid = cpu_cid_to_cid(pcp->cid);
-	mm_drop_cid(mm, pcp->cid);
+	if (cid_on_cpu(pcp->cid)) {
+		pcp->cid = cpu_cid_to_cid(pcp->cid);
+		mm_drop_cid(mm, pcp->cid);
+	}
 }

 static inline unsigned int __mm_get_cid(struct mm_struct *mm, unsigned int max_cids)