Skip to contentSkip to conversation

x86/pti: Fix various fallout

PATCH In review Thomas Gleixner 4 participants 12 messages Dec 30, 2017 – Dec 31, 2017 x86

lore
Share & tools
lore
#0 lore raw

cover x86/pti: Fix various fallout

The small series fixes the recent fallout of the x86/pti merge:

- Remove the stale local_flush_tlb() invocations from the CPU hotplug code

- Remove the stale preempt_disable/enable() pair from __native_flush_tlb()

- Fix a bogus free in the write_ldt() error path

Thanks,

tglx

---
 include/asm/tlbflush.h |   14 ++++++++------
 kernel/ldt.c           |    2 +-
 kernel/smpboot.c       |    9 ---------
 3 files changed, 9 insertions(+), 16 deletions(-)
#1 lore raw

PATCH 1/3 x86/ldt: Free the right LDT memory in write_ldt() error path

The error path in write_ldt() frees the already installed LDT memory instead of the newly allocated which cannot be installed.

Fixes f55f0501cbf6 ("x86/pti: Put the LDT in its own PGD if PTI is on")
---
 arch/x86/kernel/ldt.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/kernel/ldt.c +1 -1
@@ -421,7 +421,7 @@ static int write_ldt(void __user *ptr, u
 	 */
 	error = map_ldt_struct(mm, new_ldt, old_ldt ? !old_ldt->slot : 0);
 	if (error) {
-		free_ldt_struct(old_ldt);
+		free_ldt_struct(new_ldt);
 		goto out_unlock;
 	}
 
Reviewed-by on v1 1/3
#2 lore raw

Re: [patch 1/3] x86/ldt: Free the right LDT memory in write_ldt() error path

Quoted 2 lines · Thomas Gleixner

The error path in write_ldt() frees the already installed LDT memory instead of the newly allocated which cannot be installed.

s/newly allocated
/newly allocated one

Quoted 11 lines
Fixes f55f0501cbf6 ("x86/pti: Put the LDT in its own PGD if PTI is on")
---
 arch/x86/kernel/ldt.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

arch/x86/kernel/ldt.c static int write_ldt(void __user *ptr, u · line 427

Quoted 8 lines
arch/x86/kernel/ldt.c +1 -1
@@ -421,7 +421,7 @@ static int write_ldt(void __user *ptr, u
 	 */
 	error = map_ldt_struct(mm, new_ldt, old_ldt ? !old_ldt->slot : 0);
 	if (error) {
-		free_ldt_struct(old_ldt);
+		free_ldt_struct(new_ldt);
 		goto out_unlock;
 	}
 

This bug kind of scares me ...

Thanks,

Ingo

#3 lore raw

PATCH v2 1/3 x86/ldt: Plug memory leak in error path

In reply to Thomas Gleixner

The error path in write_ldt() tries to free old_ldt instead of the newly allocated new_ldt resulting in a memory leak. It also misses to clean up a half populated LDT pagetable, which is not a leak as it gets cleaned up when the process exits.

Free both the potentially half populated LDT pagetable and the newly allocated LDT struct. This can be done unconditionally because once a LDT is mapped subsequent maps will succeed because the PTE page is already populated and the two LDTs fit into that single page.

Fixes f55f0501cbf6 ("x86/pti: Put the LDT in its own PGD if PTI is on")
---
 arch/x86/kernel/ldt.c |    8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
arch/x86/kernel/ldt.c +7 -1
@@ -421,7 +421,13 @@ static int write_ldt(void __user *ptr, u
 	 */
 	error = map_ldt_struct(mm, new_ldt, old_ldt ? !old_ldt->slot : 0);
 	if (error) {
-		free_ldt_struct(old_ldt);
+		/*
+		 * This only can fail for the first LDT setup. If a LDT is
+		 * already installed then the PTE page is already
+		 * populated. Mop up a half populated page table.
+		 */
+		free_ldt_pgtables(mm);
+		free_ldt_struct(new_ldt);
 		goto out_unlock;
 	}
 
#4 lore raw

Re: [patch V2 1/3] x86/ldt: Plug memory leak in error path

Quoted 24 lines · Thomas Gleixner

The error path in write_ldt() tries to free old_ldt instead of the newly allocated new_ldt resulting in a memory leak. It also misses to clean up a half populated LDT pagetable, which is not a leak as it gets cleaned up when the process exits.

Free both the potentially half populated LDT pagetable and the newly allocated LDT struct. This can be done unconditionally because once a LDT is mapped subsequent maps will succeed because the PTE page is already populated and the two LDTs fit into that single page.

Fixes f55f0501cbf6 ("x86/pti: Put the LDT in its own PGD if PTI is on")

---
arch/x86/kernel/ldt.c | 8 +++++++-

1 file changed, 7 insertions(+), 1 deletion(-)

arch/x86/kernel/ldt.c static int write_ldt(void __user *ptr, u · line 428

Quoted 9 lines
arch/x86/kernel/ldt.c +5 -1
@@ -421,7 +421,13 @@ static int write_ldt(void __user *ptr, u
    */
   error = map_ldt_struct(mm, new_ldt, old_ldt ? !old_ldt->slot : 0);
   if (error) {
-        free_ldt_struct(old_ldt);
+        /*
+         * This only can fail for the first LDT setup. If a LDT is
+         * already installed then the PTE page is already
+         * populated. Mop up a half populated page table.
+         */

I liked it better with the conditional. If this ever fails due to fault injection, some silly accounting issue, a paravirt glitch, or whatever, then we'll oops.

Quoted 5 lines
arch/x86/kernel/ldt.c +2 -0
+        free_ldt_pgtables(mm);
+        free_ldt_struct(new_ldt);
       goto out_unlock;
   }
 
#5 lore raw

PATCH 2/3 x86/smpboot: Remove stale tlb flush invocations

In reply to Thomas Gleixner

smpboot_setup_warm_reset_vector() and smpboot_restore_warm_reset_vector() invoke local_flush_tlb() for no obvious reason.

Digging in history revealed that the original code in the 2.1 aera added those because the code manipulated a swapper_pg_dir pagetable entry. The pagetable manipulation was removed long ago in the 2.3 timeframe, but the tlb flush invocations stayed around forever.

Remove them along with the pointless pr_debugs which come from the same 2.1 change.

---
 arch/x86/kernel/smpboot.c |    9 ---------
 1 file changed, 9 deletions(-)
arch/x86/kernel/smpboot.c +0 -9
@@ -128,14 +128,10 @@ static inline void smpboot_setup_warm_re
 	spin_lock_irqsave(&rtc_lock, flags);
 	CMOS_WRITE(0xa, 0xf);
 	spin_unlock_irqrestore(&rtc_lock, flags);
-	local_flush_tlb();
-	pr_debug("1.\n");
 	*((volatile unsigned short *)phys_to_virt(TRAMPOLINE_PHYS_HIGH)) =
 							start_eip >> 4;
-	pr_debug("2.\n");
 	*((volatile unsigned short *)phys_to_virt(TRAMPOLINE_PHYS_LOW)) =
 							start_eip & 0xf;
-	pr_debug("3.\n");
 }
 
 static inline void smpboot_restore_warm_reset_vector(void)
@@ -143,11 +139,6 @@ static inline void smpboot_restore_warm_
 	unsigned long flags;
 
 	/*
-	 * Install writable page 0 entry to set BIOS data area.
-	 */
-	local_flush_tlb();
-
-	/*
 	 * Paranoid:  Set warm reset code and vector here back
 	 * to default values.
 	 */
Reviewed-by on v1 2/3
#6 lore raw

Re: [patch 2/3] x86/smpboot: Remove stale tlb flush invocations

Quoted 7 lines · Thomas Gleixner

smpboot_setup_warm_reset_vector() and smpboot_restore_warm_reset_vector() invoke local_flush_tlb() for no obvious reason.

Digging in history revealed that the original code in the 2.1 aera added those because the code manipulated a swapper_pg_dir pagetable entry. The pagetable manipulation was removed long ago in the 2.3 timeframe, but the tlb flush invocations stayed around forever.

s/tlb/TLB

Quoted 29 lines

Remove them along with the pointless pr_debugs which come from the same 2.1 change.

---
 arch/x86/kernel/smpboot.c |    9 ---------
 1 file changed, 9 deletions(-)
arch/x86/kernel/smpboot.c +0 -4
@@ -128,14 +128,10 @@ static inline void smpboot_setup_warm_re
 	spin_lock_irqsave(&rtc_lock, flags);
 	CMOS_WRITE(0xa, 0xf);
 	spin_unlock_irqrestore(&rtc_lock, flags);
-	local_flush_tlb();
-	pr_debug("1.\n");
 	*((volatile unsigned short *)phys_to_virt(TRAMPOLINE_PHYS_HIGH)) =
 							start_eip >> 4;
-	pr_debug("2.\n");
 	*((volatile unsigned short *)phys_to_virt(TRAMPOLINE_PHYS_LOW)) =
 							start_eip & 0xf;
-	pr_debug("3.\n");
 }
 
 static inline void smpboot_restore_warm_reset_vector(void)
@@ -143,11 +139,6 @@ static inline void smpboot_restore_warm_
 	unsigned long flags;

arch/x86/kernel/smpboot.c static inline void smpboot_restore_warm_ · line 144

Quoted 10 lines
arch/x86/kernel/smpboot.c +0 -5
 
 	/*
-	 * Install writable page 0 entry to set BIOS data area.
-	 */
-	local_flush_tlb();
-
-	/*
 	 * Paranoid:  Set warm reset code and vector here back
 	 * to default values.
 	 */

Really nice archeology! :-)

Thanks,

Ingo

#7 lore raw

PATCH 3/3 x86/mm: Remove preempt_disable/enable() from __native_flush_tlb()

In reply to Thomas Gleixner

The preempt_disable/enable() pair in __native_flush_tlb() was added in commit 5cf0791da5c1 ("x86/mm: Disable preemption during CR3 read+write") to protect the UP variant of flush_tlb_mm_range().

That preempt_disable/enable() pair should have been added to the UP variant of flush_tlb_mm_range() instead.

The UP variant was removed with commit ce4a4e565f52 ("x86/mm: Remove the UP asm/tlbflush.h code, always use the (formerly) SMP code"), but the preempt_disable/enable() pair stayed around.

The latest change to __native_flush_tlb() in commit 6fd166aae78c ("x86/mm:
Use/Fix PCID to optimize user/kernel switches") added an access to a per cpu variable outside the preempt disabled regions which makes no sense at all. __native_flush_tlb() must always be called with at least preemption disabled.

Remove the preempt_disable/enable() pair and add a WARN_ON_ONCE() to catch bad callers independent of the smp_processor_id() debugging.

---
 arch/x86/include/asm/tlbflush.h |   14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)
arch/x86/include/asm/tlbflush.h +8 -6
@@ -345,15 +345,17 @@ static inline void invalidate_user_asid(
  */
 static inline void __native_flush_tlb(void)
 {
-	invalidate_user_asid(this_cpu_read(cpu_tlbstate.loaded_mm_asid));
 	/*
-	 * 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:
+	 * Preemption or interrupts must be disabled to protect the access
+	 * to the per cpu variable and to prevent being preempted between
+	 * read_cr3() and write_cr3().
 	 */
-	preempt_disable();
+	WARN_ON_ONCE(preemptible());
+
+	invalidate_user_asid(this_cpu_read(cpu_tlbstate.loaded_mm_asid));
+
+	/* If current->mm == NULL then the read_cr3() "borrows" a mm */
 	native_write_cr3(__native_read_cr3());
-	preempt_enable();
 }
 
 /*
Reviewed-by on v1 3/3
#8 lore raw

Re: [patch 3/3] x86/mm: Remove preempt_disable/enable() from __native_flush_tlb()

The cleanup looks good to me, just a few speling nits:

Quoted 11 lines · Thomas Gleixner

The preempt_disable/enable() pair in __native_flush_tlb() was added in commit 5cf0791da5c1 ("x86/mm: Disable preemption during CR3 read+write") to protect the UP variant of flush_tlb_mm_range().

That preempt_disable/enable() pair should have been added to the UP variant of flush_tlb_mm_range() instead.

The UP variant was removed with commit ce4a4e565f52 ("x86/mm: Remove the UP asm/tlbflush.h code, always use the (formerly) SMP code"), but the preempt_disable/enable() pair stayed around.

Quoted 5 lines

The latest change to __native_flush_tlb() in commit 6fd166aae78c ("x86/mm:
Use/Fix PCID to optimize user/kernel switches") added an access to a per cpu variable outside the preempt disabled regions which makes no sense at all. __native_flush_tlb() must always be called with at least preemption disabled.

s/cpu/CPU

Quoted 22 lines

Remove the preempt_disable/enable() pair and add a WARN_ON_ONCE() to catch bad callers independent of the smp_processor_id() debugging.

---
 arch/x86/include/asm/tlbflush.h |   14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)
arch/x86/include/asm/tlbflush.h +2 -4
@@ -345,15 +345,17 @@ static inline void invalidate_user_asid(
  */
 static inline void __native_flush_tlb(void)
 {
-	invalidate_user_asid(this_cpu_read(cpu_tlbstate.loaded_mm_asid));
 	/*
-	 * 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:
+	 * Preemption or interrupts must be disabled to protect the access
+	 * to the per cpu variable and to prevent being preempted between

arch/x86/include/asm/tlbflush.h static inline void invalidate_user_asid( · line 356

Quoted 10 lines
arch/x86/include/asm/tlbflush.h +6 -2
+	 * read_cr3() and write_cr3().
 	 */
-	preempt_disable();
+	WARN_ON_ONCE(preemptible());
+
+	invalidate_user_asid(this_cpu_read(cpu_tlbstate.loaded_mm_asid));
+
+	/* If current->mm == NULL then the read_cr3() "borrows" a mm */
 	native_write_cr3(__native_read_cr3());
-	preempt_enable();

s/a mm/an mm

Thanks,

Ingo

#9 lore raw
In reply to Thomas Gleixner
Quoted 7 lines · Thomas Gleixner

The small series fixes the recent fallout of the x86/pti merge:

- Remove the stale local_flush_tlb() invocations from the CPU hotplug code

- Remove the stale preempt_disable/enable() pair from __native_flush_tlb()

- Fix a bogus free in the write_ldt() error path

Linus, I suspect -rc6 is imminent, and it would be nice to at least have the LDT error path fix in. I'll send you these fixes tomorrow, but feel free to pick it up from email if you wanted to release -rc6 today.

Thanks,

Ingo

#10 lore raw
Quoted 4 lines · Ingo Molnar

Linus, I suspect -rc6 is imminent, and it would be nice to at least have the LDT error path fix in. I'll send you these fixes tomorrow, but feel free to pick it up from email if you wanted to release -rc6 today.

I'll do rc6 tomorrow probably around this time (early afternoon PST). So I think I should be ok just waiting for your pull request.

Thanks,

Linus

#11 lore raw
Quoted 12 lines · Linus Torvalds
Quoted 6 lines · Ingo Molnar

Linus, I suspect -rc6 is imminent, and it would be nice to at least have the LDT error path fix in. I'll send you these fixes tomorrow, but feel free to pick it up from email if you wanted to release -rc6 today.

I'll do rc6 tomorrow probably around this time (early afternoon PST). So I think I should be ok just waiting for your pull request.

FWIW, I think this big is at worst just a memory leak. Once an mm has any LDT mapped, mapping a second one can't fail because the entire LDT area is under 512 pages, meaning that nothing needs to be allocated, so there's no opportunity for failure.

So it's an embarrassing bug, but not catastrophic.

Quoted 4 lines

Thanks,

Linus