All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] arm64: asid: Do not replace active_asids if already 0
@ 2018-01-04 11:17 Catalin Marinas
  2018-01-04 11:40 ` Will Deacon
  0 siblings, 1 reply; 3+ messages in thread
From: Catalin Marinas @ 2018-01-04 11:17 UTC (permalink / raw)
  To: linux-arm-kernel

Under some uncommon timing conditions, a generation check and
xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
an ASID roll-over on P2. If P2 has not seen the update to
active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
up waiting on the spinlock since the xchg() returned 0 while P2 can go
through a second ASID roll-over with (T2,A1,G2) active on P2. This
roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
their generation bumped to G3:

P1					P2
--                                      --
TTBR0.BADDR = T0
TTBR0.ASID = A0
asid_generation = G1
check_and_switch_context(T1,A1,G1)
  generation match
					check_and_switch_context(T2,A0,G0)
 				          new_context()
					    ASID roll-over
					    asid_generation = G2
					    flush_context()
					      active_asids[P1] = 0
					      asid_map[A1] = 0
					      reserved_asids[P1] = A0,G0
  xchg(active_asids, A1)
    active_asids[P1] = A1,G1
    xchg returns 0
  spin_lock_irqsave()
					    allocated ASID (T2,A1,G2)
					    asid_map[A1] = 1
					  active_asids[P2] = A1,G2
					...
					check_and_switch_context(T3,A0,G0)
					  new_context()
					    ASID roll-over
					    asid_generation = G3
					    flush_context()
					      active_asids[P1] = 0
					      asid_map[A1] = 1
					      reserved_asids[P1] = A1,G1
					      reserved_asids[P2] = A1,G2
					    allocated ASID (T3,A2,G3)
					    asid_map[A2] = 1
					  active_asids[P2] = A2,G3
  new_context()
    check_update_reserved_asid(A1,G1)
      matches reserved_asid[P1]
      reserved_asid[P1] = A1,G3
  updated T1 ASID to (T1,A1,G3)
					check_and_switch_context(T2,A1,G2)
					  new_context()
					    check_and_switch_context(A1,G2)
					      matches reserved_asids[P2]
					      reserved_asids[P2] = A1,G3
					  updated T2 ASID to (T2,A1,G3)

At this point, we have two tasks, T1 and T2 both using ASID A1 with the
latest generation G3. Any of them is allowed to be scheduled on the
other CPU leading to two different tasks with the same ASID on the same
CPU.

This patch changes the xchg to cmpxchg so that the active_asids is only
updated if non-zero to avoid a race with an ASID roll-over on a
different CPU.

Cc: Will Deacon <will.deacon@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---

We could add a cc stable as the patch is not invasive but I doubt one
could trigger this under normal circumstances. I would say the (still
small) probability is slightly increased under virtualisation when a
vCPU could be scheduled out for a longer time allowing other vCPUs to go
through a new roll-over.

An arm32 patch will follow as well.

(and we now have a formally verified ASID allocator ;))

 arch/arm64/mm/context.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/arch/arm64/mm/context.c b/arch/arm64/mm/context.c
index 1cb3bc92ae5c..1fe71b9fcf35 100644
--- a/arch/arm64/mm/context.c
+++ b/arch/arm64/mm/context.c
@@ -194,26 +194,29 @@ static u64 new_context(struct mm_struct *mm, unsigned int cpu)
 void check_and_switch_context(struct mm_struct *mm, unsigned int cpu)
 {
 	unsigned long flags;
-	u64 asid;
+	u64 asid, old_active_asid;
 
 	asid = atomic64_read(&mm->context.id);
 
 	/*
 	 * The memory ordering here is subtle.
-	 * If our ASID matches the current generation, then we update
-	 * our active_asids entry with a relaxed xchg. Racing with a
-	 * concurrent rollover means that either:
+	 * If our active_asids is non-zero and the ASID matches the current
+	 * generation, then we update the active_asids entry with a relaxed
+	 * cmpxchg. Racing with a concurrent rollover means that either:
 	 *
-	 * - We get a zero back from the xchg and end up waiting on the
+	 * - We get a zero back from the cmpxchg and end up waiting on the
 	 *   lock. Taking the lock synchronises with the rollover and so
 	 *   we are forced to see the updated generation.
 	 *
-	 * - We get a valid ASID back from the xchg, which means the
+	 * - We get a valid ASID back from the cmpxchg, which means the
 	 *   relaxed xchg in flush_context will treat us as reserved
 	 *   because atomic RmWs are totally ordered for a given location.
 	 */
-	if (!((asid ^ atomic64_read(&asid_generation)) >> asid_bits)
-	    && atomic64_xchg_relaxed(&per_cpu(active_asids, cpu), asid))
+	old_active_asid = atomic64_read(&per_cpu(active_asids, cpu));
+	if (old_active_asid &&
+	    !((asid ^ atomic64_read(&asid_generation)) >> asid_bits) &&
+	    atomic64_cmpxchg_relaxed(&per_cpu(active_asids, cpu),
+				     old_active_asid, asid))
 		goto switch_mm_fastpath;
 
 	raw_spin_lock_irqsave(&cpu_asid_lock, flags);

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH] arm64: asid: Do not replace active_asids if already 0
  2018-01-04 11:17 [PATCH] arm64: asid: Do not replace active_asids if already 0 Catalin Marinas
@ 2018-01-04 11:40 ` Will Deacon
  2018-01-04 14:18   ` Catalin Marinas
  0 siblings, 1 reply; 3+ messages in thread
From: Will Deacon @ 2018-01-04 11:40 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Catalin,

This is really awesome work!

On Thu, Jan 04, 2018 at 11:17:21AM +0000, Catalin Marinas wrote:
> Under some uncommon timing conditions, a generation check and
> xchg(active_asids, A1) in check_and_switch_context() on P1 can race with
> an ASID roll-over on P2. If P2 has not seen the update to
> active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends
> up waiting on the spinlock since the xchg() returned 0 while P2 can go
> through a second ASID roll-over with (T2,A1,G2) active on P2. This
> roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and
> active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent
> scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get
> their generation bumped to G3:
> 
> P1					P2
> --                                      --
> TTBR0.BADDR = T0
> TTBR0.ASID = A0
> asid_generation = G1
> check_and_switch_context(T1,A1,G1)
>   generation match
> 					check_and_switch_context(T2,A0,G0)
>  				          new_context()
> 					    ASID roll-over
> 					    asid_generation = G2
> 					    flush_context()
> 					      active_asids[P1] = 0
> 					      asid_map[A1] = 0
> 					      reserved_asids[P1] = A0,G0
>   xchg(active_asids, A1)
>     active_asids[P1] = A1,G1
>     xchg returns 0
>   spin_lock_irqsave()
> 					    allocated ASID (T2,A1,G2)
> 					    asid_map[A1] = 1
> 					  active_asids[P2] = A1,G2
> 					...
> 					check_and_switch_context(T3,A0,G0)
> 					  new_context()
> 					    ASID roll-over
> 					    asid_generation = G3
> 					    flush_context()
> 					      active_asids[P1] = 0
> 					      asid_map[A1] = 1
> 					      reserved_asids[P1] = A1,G1
> 					      reserved_asids[P2] = A1,G2
> 					    allocated ASID (T3,A2,G3)
> 					    asid_map[A2] = 1
> 					  active_asids[P2] = A2,G3
>   new_context()
>     check_update_reserved_asid(A1,G1)
>       matches reserved_asid[P1]
>       reserved_asid[P1] = A1,G3
>   updated T1 ASID to (T1,A1,G3)
> 					check_and_switch_context(T2,A1,G2)
> 					  new_context()
> 					    check_and_switch_context(A1,G2)
> 					      matches reserved_asids[P2]
> 					      reserved_asids[P2] = A1,G3
> 					  updated T2 ASID to (T2,A1,G3)
> 
> At this point, we have two tasks, T1 and T2 both using ASID A1 with the
> latest generation G3. Any of them is allowed to be scheduled on the
> other CPU leading to two different tasks with the same ASID on the same
> CPU.
> 
> This patch changes the xchg to cmpxchg so that the active_asids is only
> updated if non-zero to avoid a race with an ASID roll-over on a
> different CPU.
> 
> Cc: Will Deacon <will.deacon@arm.com>
> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
> ---
> 
> We could add a cc stable as the patch is not invasive but I doubt one
> could trigger this under normal circumstances. I would say the (still
> small) probability is slightly increased under virtualisation when a
> vCPU could be scheduled out for a longer time allowing other vCPUs to go
> through a new roll-over.
> 
> An arm32 patch will follow as well.
> 
> (and we now have a formally verified ASID allocator ;))

It would be cool to mention the verifier in the commit message; potentially
even including the code somewhere so that it can be used to test future
changes. For example, I did something similar for the qrwlock and pushed
the changes here:

https://git.kernel.org/pub/scm/linux/kernel/git/will/qrwlock-rmem.git/

Anyway, for the patch:

Acked-by: Will Deacon <will.deacon@arm.com>
Reviewed-by: Will Deacon <will.deacon@arm.com>

I don't see the need for stable; this race isn't going to occur in
practice.

Will

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH] arm64: asid: Do not replace active_asids if already 0
  2018-01-04 11:40 ` Will Deacon
@ 2018-01-04 14:18   ` Catalin Marinas
  0 siblings, 0 replies; 3+ messages in thread
From: Catalin Marinas @ 2018-01-04 14:18 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jan 04, 2018 at 11:40:28AM +0000, Will Deacon wrote:
> On Thu, Jan 04, 2018 at 11:17:21AM +0000, Catalin Marinas wrote:
> > (and we now have a formally verified ASID allocator ;))
> 
> It would be cool to mention the verifier in the commit message; potentially
> even including the code somewhere so that it can be used to test future
> changes.

Good idea. I pushed it here:

https://git.kernel.org/pub/scm/linux/kernel/git/cmarinas/kernel-tla.git/

(keeping the repository name generic as I may push other specs)

> Acked-by: Will Deacon <will.deacon@arm.com>
> Reviewed-by: Will Deacon <will.deacon@arm.com>

Thanks.

-- 
Catalin

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2018-01-04 14:18 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-01-04 11:17 [PATCH] arm64: asid: Do not replace active_asids if already 0 Catalin Marinas
2018-01-04 11:40 ` Will Deacon
2018-01-04 14:18   ` Catalin Marinas

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.