linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] x86/mm: disable preemption during CR3 read+write
@ 2016-08-05 13:37 Sebastian Andrzej Siewior
  2016-08-05 13:53 ` Peter Zijlstra
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Sebastian Andrzej Siewior @ 2016-08-05 13:37 UTC (permalink / raw)
  To: linux-mm, linux-kernel
  Cc: x86, Borislav Petkov, Andy Lutomirski, Rik van Riel, Mel Gorman,
	Peter Zijlstra

Usually current->mm (and therefore mm->pgd) stays the same during the
lifetime of a task so it does not matter if a task gets preempted during
the read and write of the CR3.

But then, there is this scenario on x86-UP:
TaskA is in do_exit() and exit_mm() sets current->mm = NULL followed by
mmput() -> exit_mmap() -> tlb_finish_mmu() -> tlb_flush_mmu() ->
tlb_flush_mmu_tlbonly() -> tlb_flush() -> flush_tlb_mm_range() ->
__flush_tlb_up() -> __flush_tlb() ->  __native_flush_tlb().

At this point current->mm is NULL but current->active_mm still points to
the "old" mm.
Let's preempt taskA _after_ native_read_cr3() by taskB. TaskB has its
own mm so CR3 has changed.
Now preempt back to taskA. TaskA has no ->mm set so it borrows taskB's
mm and so CR3 remains unchanged. Once taskA gets active it continues
where it was interrupted and that means it writes its old CR3 value
back. Everything is fine because userland won't need its memory
anymore.

Now the fun part. Let's preempt taskA one more time and get back to
taskB. This time switch_mm() won't do a thing because oldmm
(->active_mm) is the same as mm (as per context_switch()). So we remain
with a bad CR3 / pgd and return to userland.
The next thing that happens is handle_mm_fault() with an address for the
execution of its code in userland. handle_mm_fault() realizes that it
has a PTE with proper rights so it returns doing nothing. But the CPU
looks at the wrong pgd and insists that something is wrong and faults
again. And again. And one more time…

This pagefault circle continues until the scheduler gets tired of it and
puts another task on the CPU. It gets little difficult if the task is a
RT task with a high priority. The system will either freeze or it gets
fixed by the software watchdog thread which usually runs at RT-max prio.
But waiting for the watchdog will increase the latency of the RT task
which is no good.

Cc: stable@vger.kernel.org
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 arch/x86/include/asm/tlbflush.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h
index 4e5be94e079a..1ee065954e24 100644
--- a/arch/x86/include/asm/tlbflush.h
+++ b/arch/x86/include/asm/tlbflush.h
@@ -135,7 +135,14 @@ static inline void cr4_set_bits_and_update_boot(unsigned long mask)
 
 static inline void __native_flush_tlb(void)
 {
+	/*
+	 * if current->mm == NULL then we borrow a mm which may change during a
+	 * task switch and therefore we must not be preempted while we write CR3
+	 * back.
+	 */
+	preempt_disable();
 	native_write_cr3(native_read_cr3());
+	preempt_enable();
 }
 
 static inline void __native_flush_tlb_global_irq_disabled(void)
-- 
2.8.1

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

* Re: [PATCH] x86/mm: disable preemption during CR3 read+write
  2016-08-05 13:37 [PATCH] x86/mm: disable preemption during CR3 read+write Sebastian Andrzej Siewior
@ 2016-08-05 13:53 ` Peter Zijlstra
  2016-08-05 14:38 ` Rik van Riel
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Peter Zijlstra @ 2016-08-05 13:53 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: linux-mm, linux-kernel, x86, Borislav Petkov, Andy Lutomirski,
	Rik van Riel, Mel Gorman

On Fri, Aug 05, 2016 at 03:37:39PM +0200, Sebastian Andrzej Siewior wrote:
> diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h
> index 4e5be94e079a..1ee065954e24 100644
> --- a/arch/x86/include/asm/tlbflush.h
> +++ b/arch/x86/include/asm/tlbflush.h
> @@ -135,7 +135,14 @@ static inline void cr4_set_bits_and_update_boot(unsigned long mask)
>  
>  static inline void __native_flush_tlb(void)
>  {
> +	/*
> +	 * if current->mm == NULL then we borrow a mm which may change during a
> +	 * task switch and therefore we must not be preempted while we write CR3
> +	 * back.
> +	 */
> +	preempt_disable();
>  	native_write_cr3(native_read_cr3());
> +	preempt_enable();
>  }

Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>

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

* Re: [PATCH] x86/mm: disable preemption during CR3 read+write
  2016-08-05 13:37 [PATCH] x86/mm: disable preemption during CR3 read+write Sebastian Andrzej Siewior
  2016-08-05 13:53 ` Peter Zijlstra
@ 2016-08-05 14:38 ` Rik van Riel
  2016-08-05 15:42 ` Andy Lutomirski
  2016-08-10 18:10 ` [tip:x86/urgent] x86/mm: Disable " tip-bot for Sebastian Andrzej Siewior
  3 siblings, 0 replies; 6+ messages in thread
From: Rik van Riel @ 2016-08-05 14:38 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior, linux-mm, linux-kernel
  Cc: x86, Borislav Petkov, Andy Lutomirski, Mel Gorman, Peter Zijlstra

[-- Attachment #1: Type: text/plain, Size: 666 bytes --]

On Fri, 2016-08-05 at 15:37 +0200, Sebastian Andrzej Siewior wrote:
> 
> +++ b/arch/x86/include/asm/tlbflush.h
> @@ -135,7 +135,14 @@ static inline void
> cr4_set_bits_and_update_boot(unsigned long mask)
>  
>  static inline void __native_flush_tlb(void)
>  {
> +	/*
> +	 * if current->mm == NULL then we borrow a mm which may
> change during a
> +	 * task switch and therefore we must not be preempted while
> we write CR3
> +	 * back.
> +	 */
> +	preempt_disable();
>  	native_write_cr3(native_read_cr3());
> +	preempt_enable();
>  }

That is one subtle race!

Acked-by: Rik van Riel <riel@redhat.com>

-- 

All Rights Reversed.

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 473 bytes --]

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

* Re: [PATCH] x86/mm: disable preemption during CR3 read+write
  2016-08-05 13:37 [PATCH] x86/mm: disable preemption during CR3 read+write Sebastian Andrzej Siewior
  2016-08-05 13:53 ` Peter Zijlstra
  2016-08-05 14:38 ` Rik van Riel
@ 2016-08-05 15:42 ` Andy Lutomirski
  2016-08-05 15:52   ` Sebastian Andrzej Siewior
  2016-08-10 18:10 ` [tip:x86/urgent] x86/mm: Disable " tip-bot for Sebastian Andrzej Siewior
  3 siblings, 1 reply; 6+ messages in thread
From: Andy Lutomirski @ 2016-08-05 15:42 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: linux-mm, linux-kernel, X86 ML, Borislav Petkov, Andy Lutomirski,
	Rik van Riel, Mel Gorman, Peter Zijlstra

On Fri, Aug 5, 2016 at 6:37 AM, Sebastian Andrzej Siewior
<bigeasy@linutronix.de> wrote:
> Usually current->mm (and therefore mm->pgd) stays the same during the
> lifetime of a task so it does not matter if a task gets preempted during
> the read and write of the CR3.
>
> But then, there is this scenario on x86-UP:
> TaskA is in do_exit() and exit_mm() sets current->mm = NULL followed by
> mmput() -> exit_mmap() -> tlb_finish_mmu() -> tlb_flush_mmu() ->
> tlb_flush_mmu_tlbonly() -> tlb_flush() -> flush_tlb_mm_range() ->
> __flush_tlb_up() -> __flush_tlb() ->  __native_flush_tlb().
>
> At this point current->mm is NULL but current->active_mm still points to
> the "old" mm.
> Let's preempt taskA _after_ native_read_cr3() by taskB. TaskB has its
> own mm so CR3 has changed.
> Now preempt back to taskA. TaskA has no ->mm set so it borrows taskB's
> mm and so CR3 remains unchanged. Once taskA gets active it continues
> where it was interrupted and that means it writes its old CR3 value
> back. Everything is fine because userland won't need its memory
> anymore.

This should affect kernel threads too, right?

Acked-by: Andy Lutomirski <luto@kernel.org>

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

* Re: [PATCH] x86/mm: disable preemption during CR3 read+write
  2016-08-05 15:42 ` Andy Lutomirski
@ 2016-08-05 15:52   ` Sebastian Andrzej Siewior
  0 siblings, 0 replies; 6+ messages in thread
From: Sebastian Andrzej Siewior @ 2016-08-05 15:52 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: linux-mm, linux-kernel, X86 ML, Borislav Petkov, Andy Lutomirski,
	Rik van Riel, Mel Gorman, Peter Zijlstra

On 08/05/2016 05:42 PM, Andy Lutomirski wrote:
> 
> This should affect kernel threads too, right?

I don't think so because they don't have a MM in the first place so
they don't shouldn't need to flush a TLB. But then there is iounmap()
and vfree() for instance which does

vmap_debug_free_range()
{
   if (debug_pagealloc_enabled()) {
         vunmap_page_range(start, end);
         flush_tlb_kernel_range(start, end);
   }
}

so it looks like a candidate.

> Acked-by: Andy Lutomirski <luto@kernel.org>

Sebastian

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

* [tip:x86/urgent] x86/mm: Disable preemption during CR3 read+write
  2016-08-05 13:37 [PATCH] x86/mm: disable preemption during CR3 read+write Sebastian Andrzej Siewior
                   ` (2 preceding siblings ...)
  2016-08-05 15:42 ` Andy Lutomirski
@ 2016-08-10 18:10 ` tip-bot for Sebastian Andrzej Siewior
  3 siblings, 0 replies; 6+ messages in thread
From: tip-bot for Sebastian Andrzej Siewior @ 2016-08-10 18:10 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: bp, torvalds, luto, mgorman, bp, linux-kernel, dvlasenk,
	a.p.zijlstra, riel, hpa, tglx, brgerst, mingo, jpoimboe, bigeasy,
	peterz

Commit-ID:  5cf0791da5c162ebc14b01eb01631cfa7ed4fa6e
Gitweb:     http://git.kernel.org/tip/5cf0791da5c162ebc14b01eb01631cfa7ed4fa6e
Author:     Sebastian Andrzej Siewior <bigeasy@linutronix.de>
AuthorDate: Fri, 5 Aug 2016 15:37:39 +0200
Committer:  Ingo Molnar <mingo@kernel.org>
CommitDate: Wed, 10 Aug 2016 15:37:16 +0200

x86/mm: Disable preemption during CR3 read+write

There's a subtle preemption race on UP kernels:

Usually current->mm (and therefore mm->pgd) stays the same during the
lifetime of a task so it does not matter if a task gets preempted during
the read and write of the CR3.

But then, there is this scenario on x86-UP:

TaskA is in do_exit() and exit_mm() sets current->mm = NULL followed by:

 -> mmput()
 -> exit_mmap()
 -> tlb_finish_mmu()
 -> tlb_flush_mmu()
 -> tlb_flush_mmu_tlbonly()
 -> tlb_flush()
 -> flush_tlb_mm_range()
 -> __flush_tlb_up()
 -> __flush_tlb()
 ->  __native_flush_tlb()

At this point current->mm is NULL but current->active_mm still points to
the "old" mm.

Let's preempt taskA _after_ native_read_cr3() by taskB. TaskB has its
own mm so CR3 has changed.

Now preempt back to taskA. TaskA has no ->mm set so it borrows taskB's
mm and so CR3 remains unchanged. Once taskA gets active it continues
where it was interrupted and that means it writes its old CR3 value
back. Everything is fine because userland won't need its memory
anymore.

Now the fun part:

Let's preempt taskA one more time and get back to taskB. This
time switch_mm() won't do a thing because oldmm (->active_mm)
is the same as mm (as per context_switch()). So we remain
with a bad CR3 / PGD and return to userland.

The next thing that happens is handle_mm_fault() with an address for
the execution of its code in userland. handle_mm_fault() realizes that
it has a PTE with proper rights so it returns doing nothing. But the
CPU looks at the wrong PGD and insists that something is wrong and
faults again. And again. And one more time…

This pagefault circle continues until the scheduler gets tired of it and
puts another task on the CPU. It gets little difficult if the task is a
RT task with a high priority. The system will either freeze or it gets
fixed by the software watchdog thread which usually runs at RT-max prio.
But waiting for the watchdog will increase the latency of the RT task
which is no good.

Fix this by disabling preemption across the critical code section.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Rik van Riel <riel@redhat.com>
Acked-by: Andy Lutomirski <luto@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Borislav Petkov <bp@suse.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-mm@kvack.org
Cc: stable@vger.kernel.org
Link: http://lkml.kernel.org/r/1470404259-26290-1-git-send-email-bigeasy@linutronix.de
[ Prettified the changelog. ]
Signed-off-by: Ingo Molnar <mingo@kernel.org>
---
 arch/x86/include/asm/tlbflush.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h
index 4e5be94..6fa8594 100644
--- a/arch/x86/include/asm/tlbflush.h
+++ b/arch/x86/include/asm/tlbflush.h
@@ -135,7 +135,14 @@ static inline void cr4_set_bits_and_update_boot(unsigned long mask)
 
 static inline void __native_flush_tlb(void)
 {
+	/*
+	 * If current->mm == NULL then we borrow a mm which may change during a
+	 * task switch and therefore we must not be preempted while we write CR3
+	 * back:
+	 */
+	preempt_disable();
 	native_write_cr3(native_read_cr3());
+	preempt_enable();
 }
 
 static inline void __native_flush_tlb_global_irq_disabled(void)

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

end of thread, other threads:[~2016-08-10 20:50 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-08-05 13:37 [PATCH] x86/mm: disable preemption during CR3 read+write Sebastian Andrzej Siewior
2016-08-05 13:53 ` Peter Zijlstra
2016-08-05 14:38 ` Rik van Riel
2016-08-05 15:42 ` Andy Lutomirski
2016-08-05 15:52   ` Sebastian Andrzej Siewior
2016-08-10 18:10 ` [tip:x86/urgent] x86/mm: Disable " tip-bot for Sebastian Andrzej Siewior

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).