Commit 1e83ccd5 authored by Thomas Gleixner's avatar Thomas Gleixner Committed by Linus Torvalds
Browse files

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: 007d8428 ("sched/mmcid: Drop per CPU CID immediately when switching to per task mode")
Reported-by: default avatarShinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
Signed-off-by: default avatarThomas Gleixner <tglx@kernel.org>
Tested-by: default avatarShinichiro Kawasaki <shinichiro.kawasaki@wdc.com>
Cc: stable@vger.kernel.org
Closes: https://lore.kernel.org/aYsZrixn9b6s_2zL@shinmob


Reviewed-by: default avatarMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 939faf71
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -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));
			}
+4 −2
Original line number Diff line number Diff line
@@ -3813,9 +3813,11 @@ 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 */
	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)
{