Skip to contentSkip to conversation

x86/mm/pti: Robustness updates

PATCH Applied Thomas Gleixner 7 participants 26 messages Aug 28, 2019 – Aug 30, 2019

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover x86/mm/pti: Robustness updates

Following up on the discussions around the patch Song submitted to 'cure' a iTLB related performance regression, I picked up Song's patch which makes clone_page_tables() more robust by handling unaligned addresses proper and added one which prevents calling into the PTI code when PTI is enabled compile time, but disabled at runtime (command line or CPU not affected).

There is no point in calling into those PTI functions unconditionally. The resulting page tables are the same before and after the change which makes sense as the code clones the kernel page table into the secondary page table space which is available but not used when PTI is boot time disabled.

But even if it does not do damage today, this could have nasty side effect when the PTI code is changed, extended etc. later.

Thanks,

tglx
8<----------------

 pti.c |    8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
#1 lore raw

PATCH 1/2 x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

---
 arch/x86/mm/pti.c |    6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
arch/x86/mm/pti.c +4 -2
@@ -330,13 +330,15 @@ pti_clone_pgtable(unsigned long start, u
 
 		pud = pud_offset(p4d, addr);
 		if (pud_none(*pud)) {
-			addr += PUD_SIZE;
+			WARN_ON_ONCE(addr & PUD_MASK);
+			addr = round_up(addr + 1, PUD_SIZE);
 			continue;
 		}
 
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);
 			continue;
 		}
 
Reviewed-by on v1 1/2
#2 lore raw

Re: [patch 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 10 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

Quoted 4 lines

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

Song, did you ever root-cause the performance regression? I thought there were still some mysteries there.

#3 lore raw

Re: [patch 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 16 lines · Dave Hansen
Quoted 10 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

Quoted 4 lines

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

Quoted 4 lines

Song, did you ever root-cause the performance regression? I thought there were still some mysteries there.

See Peter's series to rework the ftrace code patching ...

#4 lore raw

Re: [patch 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 21 lines · Thomas Gleixner
Quoted 16 lines · Dave Hansen
Quoted 14 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

Quoted 6 lines
Quoted 4 lines

Song, did you ever root-cause the performance regression? I thought there were still some mysteries there.

See Peter's series to rework the ftrace code patching ...

Thanks Thomas.

Yes, in summary, enabling ftrace or kprobe-on-ftrace causes the kernel to split PMDs in kernel text mapping.

Related question: while Peter's patches fix it for 5.3 kernel, they don't apply cleanly over 5.2 kernel (which we are using). So I wonder what is the best solution for 5.2 kernel. May patch also fixes the issue:

https://lore.kernel.org/lkml/[email protected]/

How about we apply this patch to upstream 5.2 kernel?

Thanks,
Song

#5 lore raw

Re: [patch 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 31 lines · Song Liu
Quoted 19 lines · Thomas Gleixner
Quoted 16 lines · Dave Hansen
Quoted 14 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

Quoted 6 lines
Quoted 4 lines

Song, did you ever root-cause the performance regression? I thought there were still some mysteries there.

See Peter's series to rework the ftrace code patching ...

Thanks Thomas.

Yes, in summary, enabling ftrace or kprobe-on-ftrace causes the kernel to split PMDs in kernel text mapping.

Quoted 7 lines

Related question: while Peter's patches fix it for 5.3 kernel, they don't apply cleanly over 5.2 kernel (which we are using). So I wonder what is the best solution for 5.2 kernel. May patch also fixes the issue:

https://lore.kernel.org/lkml/[email protected]/

How about we apply this patch to upstream 5.2 kernel?

That's not how it works. We fix stuff upstream and it gets backported to all affected kernels not just to the one you care about.

Aside of that I really disagree with that hack. You completely fail to explain why that commit in question broke it and instead of fixing the underlying issue you create a horrible workaround.

It took me ~10 minutes to analyze the root cause and I'm just booting the test box with a proper fix which can be actually tagged for stable and can be removed from upstream again once ftrace got moved over to text poke.

I'll post it once it's confirmed to work and I wrote a comprehensible changelog.

Thanks,

tglx

#6 lore raw

Re: [patch 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 40 lines · Thomas Gleixner
Quoted 35 lines · Song Liu
Quoted 19 lines · Thomas Gleixner
Quoted 16 lines · Dave Hansen
Quoted 14 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

Quoted 6 lines
Quoted 4 lines

Song, did you ever root-cause the performance regression? I thought there were still some mysteries there.

See Peter's series to rework the ftrace code patching ...

Thanks Thomas.

Yes, in summary, enabling ftrace or kprobe-on-ftrace causes the kernel to split PMDs in kernel text mapping.

Related question: while Peter's patches fix it for 5.3 kernel, they don't apply cleanly over 5.2 kernel (which we are using). So I wonder what is the best solution for 5.2 kernel. May patch also fixes the issue:

Quoted 6 lines
Quoted 3 lines

https://lore.kernel.org/lkml/[email protected]/

How about we apply this patch to upstream 5.2 kernel?

That's not how it works. We fix stuff upstream and it gets backported to all affected kernels not just to the one you care about.

Agreed. I am trying to back port Peter's patch set to 5.2 kernel. There are 9 dependencies and some manual changes.

Quoted 11 lines

Aside of that I really disagree with that hack. You completely fail to explain why that commit in question broke it and instead of fixing the underlying issue you create a horrible workaround.

It took me ~10 minutes to analyze the root cause and I'm just booting the test box with a proper fix which can be actually tagged for stable and can be removed from upstream again once ftrace got moved over to text poke.

I'll post it once it's confirmed to work and I wrote a comprehensible changelog.

This sounds great. Thanks!

Song

#7 lore raw

PATCH 1/1 x86/mm/cpa: Prevent large page split when ftrace flips RW on kernel text

ftrace does not use text_poke() for enabling trace functionality. It uses its own mechanism and flips the whole kernel text to RW and back to RO.

The CPA rework removed a loop based check of 4k pages which tried to preserve a large page by checking each 4k page whether the change would actually cover all pages in the large page.

This resulted in endless loops for nothing as in testing it turned out that it actually never preserved anything. Of course testing missed to include ftrace, which is the one and only case which benefitted from the 4k loop.

As a consequence enabling function tracing or ftrace based kprobes results in a full 4k split of the kernel text, which affects iTLB performance.

The kernel RO protection is the only valid case where this can actually preserve large pages.

All other static protections (RO data, data NX, PCI, BIOS) are truly static. So a conflict with those protections which results in a split should only ever happen when a change of memory next to a protected region is attempted. But these conflicts are rightfully splitting the large page to preserve the protected regions. In fact a change to the protected regions itself is a bug and is warned about.

Add an exception for the static protection check for kernel text RO when the to be changed region spawns a full large page which allows to preserve the large mappings. This also prevents the syslog to be spammed about CPA violations when ftrace is used.

The exception needs to be removed once ftrace switched over to text_poke() which avoids the whole issue.

Fixes 585948f4f695 ("x86/mm/cpa: Avoid the 4k pages check completely")
---
 arch/x86/mm/pageattr.c |   26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)
arch/x86/mm/pageattr.c +18 -8
@@ -516,7 +516,7 @@ static inline void check_conflict(int wa
  */
 static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
 					  unsigned long pfn, unsigned long npg,
-					  int warnlvl)
+					  unsigned long lpsize, int warnlvl)
 {
 	pgprotval_t forbidden, res;
 	unsigned long end;
@@ -535,9 +535,17 @@ static inline pgprot_t static_protection
 	check_conflict(warnlvl, prot, res, start, end, pfn, "Text NX");
 	forbidden = res;
 
-	res = protect_kernel_text_ro(start, end);
-	check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
-	forbidden |= res;
+	/*
+	 * Special case to preserve a large page. If the change spawns the
+	 * full large page mapping then there is no point to split it
+	 * up. Happens with ftrace and is going to be removed once ftrace
+	 * switched to text_poke().
+	 */
+	if (lpsize != (npg * PAGE_SIZE) || (start & (lpsize - 1))) {
+		res = protect_kernel_text_ro(start, end);
+		check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
+		forbidden |= res;
+	}
 
 	/* Check the PFN directly */
 	res = protect_pci_bios(pfn, pfn + npg - 1);
@@ -819,7 +827,7 @@ static int __should_split_large_page(pte
 	 * extra conditional required here.
 	 */
 	chk_prot = static_protections(old_prot, lpaddr, old_pfn, numpages,
-				      CPA_CONFLICT);
+				      psize, CPA_CONFLICT);
 
 	if (WARN_ON_ONCE(pgprot_val(chk_prot) != pgprot_val(old_prot))) {
 		/*
@@ -855,7 +863,7 @@ static int __should_split_large_page(pte
 	 * protection requirement in the large page.
 	 */
 	new_prot = static_protections(req_prot, lpaddr, old_pfn, numpages,
-				      CPA_DETECT);
+				      psize, CPA_DETECT);
 
 	/*
 	 * If there is a conflict, split the large page.
@@ -906,7 +914,8 @@ static void split_set_pte(struct cpa_dat
 	if (!cpa->force_static_prot)
 		goto set;
 
-	prot = static_protections(ref_prot, address, pfn, npg, CPA_PROTECT);
+	/* Hand in lpsize = 0 to enforce the protection mechanism */
+	prot = static_protections(ref_prot, address, pfn, npg, 0, CPA_PROTECT);
 
 	if (pgprot_val(prot) == pgprot_val(ref_prot))
 		goto set;
@@ -1503,7 +1512,8 @@ static int __change_page_attr(struct cpa
 		pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
 
 		cpa_inc_4k_install();
-		new_prot = static_protections(new_prot, address, pfn, 1,
+		/* Hand in lpsize = 0 to enforce the protection mechanism */
+		new_prot = static_protections(new_prot, address, pfn, 1, 0,
 					      CPA_PROTECT);
 
 		new_prot = pgprot_clear_protnone_bits(new_prot);
#8 lore raw

Re: [PATCH] x86/mm/cpa: Prevent large page split when ftrace flips RW on kernel text

Quoted 32 lines · Thomas Gleixner

ftrace does not use text_poke() for enabling trace functionality. It uses its own mechanism and flips the whole kernel text to RW and back to RO.

The CPA rework removed a loop based check of 4k pages which tried to preserve a large page by checking each 4k page whether the change would actually cover all pages in the large page.

This resulted in endless loops for nothing as in testing it turned out that it actually never preserved anything. Of course testing missed to include ftrace, which is the one and only case which benefitted from the 4k loop.

As a consequence enabling function tracing or ftrace based kprobes results in a full 4k split of the kernel text, which affects iTLB performance.

The kernel RO protection is the only valid case where this can actually preserve large pages.

All other static protections (RO data, data NX, PCI, BIOS) are truly static. So a conflict with those protections which results in a split should only ever happen when a change of memory next to a protected region is attempted. But these conflicts are rightfully splitting the large page to preserve the protected regions. In fact a change to the protected regions itself is a bug and is warned about.

Add an exception for the static protection check for kernel text RO when the to be changed region spawns a full large page which allows to preserve the large mappings. This also prevents the syslog to be spammed about CPA violations when ftrace is used.

Quoted 7 lines

The exception needs to be removed once ftrace switched over to text_poke() which avoids the whole issue.

Fixes 585948f4f695 ("x86/mm/cpa: Avoid the 4k pages check completely")

This looks great. Much cleaner than my workaround.

Thanks!

Reviewed-and-tested-by Song Liu <[email protected]>

We need this for v4.20 to v5.3 (assuming Peter's patches will land in 5.4).

Quoted 74 lines

---
arch/x86/mm/pageattr.c | 26 ++++++++++++++++++--------

1 file changed, 18 insertions(+), 8 deletions(-)
arch/x86/mm/pageattr.c +18 -8
@@ -516,7 +516,7 @@ static inline void check_conflict(int wa
 */
 static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
 					  unsigned long pfn, unsigned long npg,
-					  int warnlvl)
+					  unsigned long lpsize, int warnlvl)
 {
 	pgprotval_t forbidden, res;
 	unsigned long end;
@@ -535,9 +535,17 @@ static inline pgprot_t static_protection
 	check_conflict(warnlvl, prot, res, start, end, pfn, "Text NX");
 	forbidden = res;
 
-	res = protect_kernel_text_ro(start, end);
-	check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
-	forbidden |= res;
+	/*
+	 * Special case to preserve a large page. If the change spawns the
+	 * full large page mapping then there is no point to split it
+	 * up. Happens with ftrace and is going to be removed once ftrace
+	 * switched to text_poke().
+	 */
+	if (lpsize != (npg * PAGE_SIZE) || (start & (lpsize - 1))) {
+		res = protect_kernel_text_ro(start, end);
+		check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
+		forbidden |= res;
+	}
 
 	/* Check the PFN directly */
 	res = protect_pci_bios(pfn, pfn + npg - 1);
@@ -819,7 +827,7 @@ static int __should_split_large_page(pte
 	 * extra conditional required here.
 	 */
 	chk_prot = static_protections(old_prot, lpaddr, old_pfn, numpages,
-				      CPA_CONFLICT);
+				      psize, CPA_CONFLICT);
 
 	if (WARN_ON_ONCE(pgprot_val(chk_prot) != pgprot_val(old_prot))) {
 		/*
@@ -855,7 +863,7 @@ static int __should_split_large_page(pte
 	 * protection requirement in the large page.
 	 */
 	new_prot = static_protections(req_prot, lpaddr, old_pfn, numpages,
-				      CPA_DETECT);
+				      psize, CPA_DETECT);
 
 	/*
 	 * If there is a conflict, split the large page.
@@ -906,7 +914,8 @@ static void split_set_pte(struct cpa_dat
 	if (!cpa->force_static_prot)
 		goto set;
 
-	prot = static_protections(ref_prot, address, pfn, npg, CPA_PROTECT);
+	/* Hand in lpsize = 0 to enforce the protection mechanism */
+	prot = static_protections(ref_prot, address, pfn, npg, 0, CPA_PROTECT);
 
 	if (pgprot_val(prot) == pgprot_val(ref_prot))
 		goto set;
@@ -1503,7 +1512,8 @@ static int __change_page_attr(struct cpa
 		pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
 
 		cpa_inc_4k_install();
-		new_prot = static_protections(new_prot, address, pfn, 1,
+		/* Hand in lpsize = 0 to enforce the protection mechanism */
+		new_prot = static_protections(new_prot, address, pfn, 1, 0,
 					      CPA_PROTECT);
 
 		new_prot = pgprot_clear_protnone_bits(new_prot);
maintainer Acked-by on v1 1/1
#9 lore raw

Re: [PATCH] x86/mm/cpa: Prevent large page split when ftrace flips RW on kernel text

In reply to Thomas Gleixner
Quoted 22 lines · Thomas Gleixner
 arch/x86/mm/pageattr.c |   26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)
arch/x86/mm/pageattr.c +2 -4
@@ -516,7 +516,7 @@ static inline void check_conflict(int wa
  */
 static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
 					  unsigned long pfn, unsigned long npg,
-					  int warnlvl)
+					  unsigned long lpsize, int warnlvl)
 {
 	pgprotval_t forbidden, res;
 	unsigned long end;
@@ -535,9 +535,17 @@ static inline pgprot_t static_protection
 	check_conflict(warnlvl, prot, res, start, end, pfn, "Text NX");
 	forbidden = res;
 
-	res = protect_kernel_text_ro(start, end);
-	check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
-	forbidden |= res;
+	/*

arch/x86/mm/pageattr.c static inline pgprot_t static_protection · line 548

Quoted 10 lines
arch/x86/mm/pageattr.c +10 -0
+	 * Special case to preserve a large page. If the change spawns the
+	 * full large page mapping then there is no point to split it
+	 * up. Happens with ftrace and is going to be removed once ftrace
+	 * switched to text_poke().
+	 */
+	if (lpsize != (npg * PAGE_SIZE) || (start & (lpsize - 1))) {
+		res = protect_kernel_text_ro(start, end);
+		check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
+		forbidden |= res;
+	}

Right, so this allows the RW (doesn't enforce RO) and thereby doesn't force split, when it is a whole large page.

Quoted 13 lines
arch/x86/mm/pageattr.c +1 -1
 	/* Check the PFN directly */
 	res = protect_pci_bios(pfn, pfn + npg - 1);
@@ -819,7 +827,7 @@ static int __should_split_large_page(pte
 	 * extra conditional required here.
 	 */
 	chk_prot = static_protections(old_prot, lpaddr, old_pfn, numpages,
-				      CPA_CONFLICT);
+				      psize, CPA_CONFLICT);
 
 	if (WARN_ON_ONCE(pgprot_val(chk_prot) != pgprot_val(old_prot))) {
 		/*

arch/x86/mm/pageattr.c static int __should_split_large_page(pte · line 869

Quoted 8 lines
arch/x86/mm/pageattr.c +1 -1
@@ -855,7 +863,7 @@ static int __should_split_large_page(pte
 	 * protection requirement in the large page.
 	 */
 	new_prot = static_protections(req_prot, lpaddr, old_pfn, numpages,
-				      CPA_DETECT);
+				      psize, CPA_DETECT);
 
 	/*
 	 * If there is a conflict, split the large page.

And these are the callsites in __should_split_large_page(), and you provide psize, and therefore we allow RW to preserve the large pages on the kernel text.

arch/x86/mm/pageattr.c static void split_set_pte(struct cpa_dat · line 918

Quoted 7 lines
arch/x86/mm/pageattr.c +2 -1
@@ -906,7 +914,8 @@ static void split_set_pte(struct cpa_dat
 	if (!cpa->force_static_prot)
 		goto set;
 
-	prot = static_protections(ref_prot, address, pfn, npg, CPA_PROTECT);
+	/* Hand in lpsize = 0 to enforce the protection mechanism */
+	prot = static_protections(ref_prot, address, pfn, npg, 0, CPA_PROTECT);

This is when we've already decided to split, in which case we might as well enforce the normal rules, and .lpsize=0 does just that.

arch/x86/mm/pageattr.c static int __change_page_attr(struct cpa · line 1517

Quoted 11 lines
arch/x86/mm/pageattr.c +2 -1
 	if (pgprot_val(prot) == pgprot_val(ref_prot))
 		goto set;
@@ -1503,7 +1512,8 @@ static int __change_page_attr(struct cpa
 		pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
 
 		cpa_inc_4k_install();
-		new_prot = static_protections(new_prot, address, pfn, 1,
+		/* Hand in lpsize = 0 to enforce the protection mechanism */
+		new_prot = static_protections(new_prot, address, pfn, 1, 0,
 					      CPA_PROTECT);

And here we check the protections of a single 4k page, in which case large pages are irrelevant and again .lpsize=0 disables the new code.

arch/x86/mm/pageattr.c static int __change_page_attr(struct cpa · line 1519

Quoted 2 lines
arch/x86/mm/pageattr.c +0 -0
 		new_prot = pgprot_clear_protnone_bits(new_prot);

That all seems OK I suppose.

#10 lore raw

[tip: x86/urgent] x86/mm/cpa: Prevent large page split when ftrace flips RW on kernel text

In reply to Thomas Gleixner
The following commit has been merged into the x86/urgent branch of tip:

The following commit has been merged into the x86/urgent branch of tip:

Commit-ID:     7af0145067bc429a09ac4047b167c0971c9f0dc7
Gitweb:        https://git.kernel.org/tip/7af0145067bc429a09ac4047b167c0971c9f0dc7
Author:        Thomas Gleixner <[email protected]>
AuthorDate:    Thu, 29 Aug 2019 00:31:34 +02:00
Committer:     Thomas Gleixner <[email protected]>
CommitterDate: Thu, 29 Aug 2019 20:48:44 +02:00

x86/mm/cpa: Prevent large page split when ftrace flips RW on kernel text

ftrace does not use text_poke() for enabling trace functionality. It uses its own mechanism and flips the whole kernel text to RW and back to RO.

The CPA rework removed a loop based check of 4k pages which tried to preserve a large page by checking each 4k page whether the change would actually cover all pages in the large page.

This resulted in endless loops for nothing as in testing it turned out that it actually never preserved anything. Of course testing missed to include ftrace, which is the one and only case which benefitted from the 4k loop.

As a consequence enabling function tracing or ftrace based kprobes results in a full 4k split of the kernel text, which affects iTLB performance.

The kernel RO protection is the only valid case where this can actually preserve large pages.

All other static protections (RO data, data NX, PCI, BIOS) are truly static. So a conflict with those protections which results in a split should only ever happen when a change of memory next to a protected region is attempted. But these conflicts are rightfully splitting the large page to preserve the protected regions. In fact a change to the protected regions itself is a bug and is warned about.

Add an exception for the static protection check for kernel text RO when the to be changed region spawns a full large page which allows to preserve the large mappings. This also prevents the syslog to be spammed about CPA violations when ftrace is used.

The exception needs to be removed once ftrace switched over to text_poke() which avoids the whole issue.

---
 arch/x86/mm/pageattr.c | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)
arch/x86/mm/pageattr.c +18 -8
index 6a9a77a..e14e95e 100644
@@ -516,7 +516,7 @@ static inline void check_conflict(int warnlvl, pgprot_t prot, pgprotval_t val,
  */
 static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
 					  unsigned long pfn, unsigned long npg,
-					  int warnlvl)
+					  unsigned long lpsize, int warnlvl)
 {
 	pgprotval_t forbidden, res;
 	unsigned long end;
@@ -535,9 +535,17 @@ static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
 	check_conflict(warnlvl, prot, res, start, end, pfn, "Text NX");
 	forbidden = res;
 
-	res = protect_kernel_text_ro(start, end);
-	check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
-	forbidden |= res;
+	/*
+	 * Special case to preserve a large page. If the change spawns the
+	 * full large page mapping then there is no point to split it
+	 * up. Happens with ftrace and is going to be removed once ftrace
+	 * switched to text_poke().
+	 */
+	if (lpsize != (npg * PAGE_SIZE) || (start & (lpsize - 1))) {
+		res = protect_kernel_text_ro(start, end);
+		check_conflict(warnlvl, prot, res, start, end, pfn, "Text RO");
+		forbidden |= res;
+	}
 
 	/* Check the PFN directly */
 	res = protect_pci_bios(pfn, pfn + npg - 1);
@@ -819,7 +827,7 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
 	 * extra conditional required here.
 	 */
 	chk_prot = static_protections(old_prot, lpaddr, old_pfn, numpages,
-				      CPA_CONFLICT);
+				      psize, CPA_CONFLICT);
 
 	if (WARN_ON_ONCE(pgprot_val(chk_prot) != pgprot_val(old_prot))) {
 		/*
@@ -855,7 +863,7 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
 	 * protection requirement in the large page.
 	 */
 	new_prot = static_protections(req_prot, lpaddr, old_pfn, numpages,
-				      CPA_DETECT);
+				      psize, CPA_DETECT);
 
 	/*
 	 * If there is a conflict, split the large page.
@@ -906,7 +914,8 @@ static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn,
 	if (!cpa->force_static_prot)
 		goto set;
 
-	prot = static_protections(ref_prot, address, pfn, npg, CPA_PROTECT);
+	/* Hand in lpsize = 0 to enforce the protection mechanism */
+	prot = static_protections(ref_prot, address, pfn, npg, 0, CPA_PROTECT);
 
 	if (pgprot_val(prot) == pgprot_val(ref_prot))
 		goto set;
@@ -1503,7 +1512,8 @@ static int __change_page_attr(struct cpa_data *cpa, int primary)
 		pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
 
 		cpa_inc_4k_install();
-		new_prot = static_protections(new_prot, address, pfn, 1,
+		/* Hand in lpsize = 0 to enforce the protection mechanism */
+		new_prot = static_protections(new_prot, address, pfn, 1, 0,
 					      CPA_PROTECT);
 
 		new_prot = pgprot_clear_protnone_bits(new_prot);
#11 lore raw

Re: [patch 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

In reply to Thomas Gleixner
Quoted 9 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

s/independent
/independently

s/exist
/exists

Quoted 4 lines

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

s/wrongly
/incorrectly

s/robustness sake
/robustness's sake

s/proper
/properly

With that:

arch/x86/mm/pti.c pti_clone_pgtable(unsigned long start, u · line 341

Quoted 13 lines
arch/x86/mm/pti.c +4 -2
 		pud = pud_offset(p4d, addr);
 		if (pud_none(*pud)) {
-			addr += PUD_SIZE;
+			WARN_ON_ONCE(addr & PUD_MASK);
+			addr = round_up(addr + 1, PUD_SIZE);
 			continue;
 		}
 
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);

So given that PUD_MASK and PMD_MASK are masking out the *offset*:

arch/x86/include/asm/pgtable_64_types.h:#define PMD_MASK (~(PMD_SIZE - 1))

Didn't we want something like:

			WARN_ON_ONCE(addr & ~PUD_MASK);

			WARN_ON_ONCE(addr & ~PMD_MASK);

to warn about an unaligned 'addr', or am I misreading the intent here?

Thanks,

Ingo

#12 lore raw

Re: [patch 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 10 lines · Ingo Molnar

arch/x86/mm/pti.c pti_clone_pgtable(unsigned long start, u · line 341

Quoted 5 lines
arch/x86/mm/pti.c +2 -1
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);

So given that PUD_MASK and PMD_MASK are masking out the *offset*:

arch/x86/include/asm/pgtable_64_types.h:#define PMD_MASK (~(PMD_SIZE - 1))

Quoted 7 lines

Didn't we want something like:

			WARN_ON_ONCE(addr & ~PUD_MASK);

			WARN_ON_ONCE(addr & ~PMD_MASK);

to warn about an unaligned 'addr', or am I misreading the intent here?

Bah, right you are...

#13 lore raw

PATCH v2 1/2 x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

In reply to Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent of the mapping level (PUD/PMD/PTE) the next higher mapping exist.

If that's not the case the unaligned address can be incremented by PUD or PMD size wrongly. All callers supply mapped and/or aligned addresses, but for robustness sake, it's better to handle that case proper and to emit a warning.

[ tglx: Rewrote changelog and added WARN_ON_ONCE() ]

---
V2: Negate P[UM]D_MASK for checking whether the offset part is 0

---
 arch/x86/mm/pti.c |    6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
arch/x86/mm/pti.c +4 -2
@@ -330,13 +330,15 @@ pti_clone_pgtable(unsigned long start, u
 
 		pud = pud_offset(p4d, addr);
 		if (pud_none(*pud)) {
-			addr += PUD_SIZE;
+			WARN_ON_ONCE(addr & ~PUD_MASK);
+			addr = round_up(addr + 1, PUD_SIZE);
 			continue;
 		}
 
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & ~PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);
 			continue;
 		}
 
#14 lore raw

Re: [patch V2 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 7 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independent

Bah. I'm a moron. Forgot to fix the spell checker issues.

#15 lore raw

PATCH v3 1/2 x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

In reply to Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independently of the mapping level (PUD/PMD/PTE) the next higher mapping exists.

If that's not the case the unaligned address can be incremented by PUD or PMD size incorrectly. All callers supply mapped and/or aligned addresses, but for the sake of robustness it's better to handle that case properly and to emit a warning.

[ tglx: Rewrote changelog and added WARN_ON_ONCE() ]

---
V2: Negate P[UM]D_MASK for checking whether the offset part is 0
V3: Fix changelog

---
 arch/x86/mm/pti.c |    6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
arch/x86/mm/pti.c +4 -2
@@ -330,13 +330,15 @@ pti_clone_pgtable(unsigned long start, u
 
 		pud = pud_offset(p4d, addr);
 		if (pud_none(*pud)) {
-			addr += PUD_SIZE;
+			WARN_ON_ONCE(addr & ~PUD_MASK);
+			addr = round_up(addr + 1, PUD_SIZE);
 			continue;
 		}
 
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & ~PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);
 			continue;
 		}
 
Reviewed-by on v3 1/2
#16 lore raw

Re: [patch V3 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

Quoted 35 lines · Thomas Gleixner

From: Song Liu <[email protected]>

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independently of the mapping level (PUD/PMD/PTE) the next higher mapping exists.

If that's not the case the unaligned address can be incremented by PUD or PMD size incorrectly. All callers supply mapped and/or aligned addresses, but for the sake of robustness it's better to handle that case properly and to emit a warning.

[ tglx: Rewrote changelog and added WARN_ON_ONCE() ]

---
V2: Negate P[UM]D_MASK for checking whether the offset part is 0
V3: Fix changelog

---
 arch/x86/mm/pti.c |    6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
arch/x86/mm/pti.c +2 -1
@@ -330,13 +330,15 @@ pti_clone_pgtable(unsigned long start, u
 
 		pud = pud_offset(p4d, addr);
 		if (pud_none(*pud)) {
-			addr += PUD_SIZE;
+			WARN_ON_ONCE(addr & ~PUD_MASK);
+			addr = round_up(addr + 1, PUD_SIZE);

arch/x86/mm/pti.c pti_clone_pgtable(unsigned long start, u · line 343

Quoted 10 lines
arch/x86/mm/pti.c +2 -1
 			continue;
 		}
 
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & ~PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);
 			continue;
 		}

Thanks,

Ingo

merge bot Applied
#17 lore raw

[tip: x86/pti] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

In reply to Thomas Gleixner
The following commit has been merged into the x86/pti branch of tip:

The following commit has been merged into the x86/pti branch of tip:

Commit-ID:     825d0b73cd7526b0bb186798583fae810091cbac
Gitweb:        https://git.kernel.org/tip/825d0b73cd7526b0bb186798583fae810091cbac
Author:        Song Liu <[email protected]>
AuthorDate:    Wed, 28 Aug 2019 23:54:55 +02:00
Committer:     Thomas Gleixner <[email protected]>
CommitterDate: Thu, 29 Aug 2019 20:52:52 +02:00

x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

pti_clone_pmds() assumes that the supplied address is either:

- properly PUD/PMD aligned
or
- the address is actually mapped which means that independently of the mapping level (PUD/PMD/PTE) the next higher mapping exists.

If that's not the case the unaligned address can be incremented by PUD or PMD size incorrectly. All callers supply mapped and/or aligned addresses, but for the sake of robustness it's better to handle that case properly and to emit a warning.

[ tglx: Rewrote changelog and added WARN_ON_ONCE() ]

---
 arch/x86/mm/pti.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
arch/x86/mm/pti.c +4 -2
index b196524..a24487b 100644
@@ -330,13 +330,15 @@ pti_clone_pgtable(unsigned long start, unsigned long end,
 
 		pud = pud_offset(p4d, addr);
 		if (pud_none(*pud)) {
-			addr += PUD_SIZE;
+			WARN_ON_ONCE(addr & ~PUD_MASK);
+			addr = round_up(addr + 1, PUD_SIZE);
 			continue;
 		}
 
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & ~PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);
 			continue;
 		}
 
Reviewed-by on v3 1/2
#18 lore raw

Re: [patch V3 1/2] x86/mm/pti: Handle unaligned address gracefully in pti_clone_pagetable()

In reply to Thomas Gleixner
Quoted 10 lines · Thomas Gleixner
arch/x86/mm/pti.c +2 -1
@@ -330,13 +330,15 @@ pti_clone_pgtable(unsigned long start, u
 
 		pud = pud_offset(p4d, addr);
 		if (pud_none(*pud)) {
-			addr += PUD_SIZE;
+			WARN_ON_ONCE(addr & ~PUD_MASK);
+			addr = round_up(addr + 1, PUD_SIZE);
 			continue;

arch/x86/mm/pti.c pti_clone_pgtable(unsigned long start, u · line 344

Quoted 10 lines
arch/x86/mm/pti.c +2 -1
 		}
 
 		pmd = pmd_offset(pud, addr);
 		if (pmd_none(*pmd)) {
-			addr += PMD_SIZE;
+			WARN_ON_ONCE(addr & ~PMD_MASK);
+			addr = round_up(addr + 1, PMD_SIZE);
 			continue;
 		}
 
#19 lore raw

PATCH 2/2 x86/mm/pti: Do not invoke PTI functions when PTI is disabled

In reply to Thomas Gleixner

When PTI is disabled at boot time either because the CPU is not affected or PTI has been disabled on the command line, the boot code still calls into pti_finalize() which then unconditionally invokes:

pti_clone_entry_text()
pti_clone_kernel_text()

pti_clone_kernel_text() was called unconditionally before the 32bit support was added and 32bit added the call to pti_clone_entry_text().

The call has no side effects as cloning the page tables into the available second one, which was allocated for PTI does not create damage. But it does not make sense either and in case that this functionality would be extended later this might actually lead to hard to diagnose issue.

Neither function should be called when PTI is runtime disabled. Make the invocation conditional.

---
 arch/x86/mm/pti.c |    2 ++
 1 file changed, 2 insertions(+)
arch/x86/mm/pti.c +2 -0
@@ -668,6 +668,8 @@ void __init pti_init(void)
  */
 void pti_finalize(void)
 {
+	if (!boot_cpu_has(X86_FEATURE_PTI))
+		return;
 	/*
 	 * We need to clone everything (again) that maps parts of the
 	 * kernel image.
Reviewed-by on v1 2/2
#20 lore raw

Re: [patch 2/2] x86/mm/pti: Do not invoke PTI functions when PTI is disabled

Quoted 2 lines · Thomas Gleixner

Neither function should be called when PTI is runtime disabled. Make the invocation conditional.

Thanks for sending that out. My impressions from a look through it matched your changelog.

Acked-by on v1 2/2
#21 lore raw

Re: [patch 2/2] x86/mm/pti: Do not invoke PTI functions when PTI is disabled

In reply to Thomas Gleixner
Quoted 19 lines · Thomas Gleixner

When PTI is disabled at boot time either because the CPU is not affected or PTI has been disabled on the command line, the boot code still calls into pti_finalize() which then unconditionally invokes:

pti_clone_entry_text()
pti_clone_kernel_text()

pti_clone_kernel_text() was called unconditionally before the 32bit support was added and 32bit added the call to pti_clone_entry_text().

The call has no side effects as cloning the page tables into the available second one, which was allocated for PTI does not create damage. But it does not make sense either and in case that this functionality would be extended later this might actually lead to hard to diagnose issue.

Quoted 4 lines

Neither function should be called when PTI is runtime disabled. Make the invocation conditional.

Acked-by on v1 2/2
#22 lore raw

Re: [patch 2/2] x86/mm/pti: Do not invoke PTI functions when PTI is disabled

In reply to Thomas Gleixner
Quoted 7 lines · Thomas Gleixner

When PTI is disabled at boot time either because the CPU is not affected or PTI has been disabled on the command line, the boot code still calls into pti_finalize() which then unconditionally invokes:

pti_clone_entry_text()
pti_clone_kernel_text()

Quoted 7 lines

pti_clone_kernel_text() was called unconditionally before the 32bit support was added and 32bit added the call to pti_clone_entry_text().

The call has no side effects as cloning the page tables into the available second one, which was allocated for PTI does not create damage. But it does not make sense either and in case that this functionality would be extended later this might actually lead to hard to diagnose issue.

s/issue/issues

Quoted 11 lines

Neither function should be called when PTI is runtime disabled. Make the invocation conditional.

---
 arch/x86/mm/pti.c |    2 ++
 1 file changed, 2 insertions(+)

arch/x86/mm/pti.c void __init pti_init(void) · line 675

Quoted 8 lines
arch/x86/mm/pti.c +2 -0
@@ -668,6 +668,8 @@ void __init pti_init(void)
  */
 void pti_finalize(void)
 {
+	if (!boot_cpu_has(X86_FEATURE_PTI))
+		return;
 	/*
 	 * We need to clone everything (again) that maps parts of the
 	 * kernel image.

Thanks,

Ingo

#23 lore raw

[tip: x86/pti] x86/mm/pti: Do not invoke PTI functions when PTI is disabled

In reply to Thomas Gleixner
The following commit has been merged into the x86/pti branch of tip:

The following commit has been merged into the x86/pti branch of tip:

Commit-ID:     990784b57731192b7d90c8d4049e6318d81e887d
Gitweb:        https://git.kernel.org/tip/990784b57731192b7d90c8d4049e6318d81e887d
Author:        Thomas Gleixner <[email protected]>
AuthorDate:    Wed, 28 Aug 2019 16:24:47 +02:00
Committer:     Thomas Gleixner <[email protected]>
CommitterDate: Thu, 29 Aug 2019 20:52:53 +02:00

x86/mm/pti: Do not invoke PTI functions when PTI is disabled

When PTI is disabled at boot time either because the CPU is not affected or PTI has been disabled on the command line, the boot code still calls into pti_finalize() which then unconditionally invokes:

pti_clone_entry_text()
pti_clone_kernel_text()

pti_clone_kernel_text() was called unconditionally before the 32bit support was added and 32bit added the call to pti_clone_entry_text().

The call has no side effects as cloning the page tables into the available second one, which was allocated for PTI does not create damage. But it does not make sense either and in case that this functionality would be extended later this might actually lead to hard to diagnose issues.

Neither function should be called when PTI is runtime disabled. Make the invocation conditional.

---
 arch/x86/mm/pti.c | 2 ++
 1 file changed, 2 insertions(+)
arch/x86/mm/pti.c +2 -0
index a24487b..7f21404 100644
@@ -668,6 +668,8 @@ void __init pti_init(void)
  */
 void pti_finalize(void)
 {
+	if (!boot_cpu_has(X86_FEATURE_PTI))
+		return;
 	/*
 	 * We need to clone everything (again) that maps parts of the
 	 * kernel image.
Reviewed-by on v1 2/2
#24 lore raw

Re: [patch 2/2] x86/mm/pti: Do not invoke PTI functions when PTI is disabled

In reply to Thomas Gleixner

arch/x86/mm/pti.c void __init pti_init(void) · line 675

Quoted 12 lines · Thomas Gleixner
arch/x86/mm/pti.c +2 -0
@@ -668,6 +668,8 @@ void __init pti_init(void)
  */
 void pti_finalize(void)
 {
+	if (!boot_cpu_has(X86_FEATURE_PTI))
+		return;
 	/*
 	 * We need to clone everything (again) that maps parts of the
 	 * kernel image.
maintainer Acked-by on the v1 series
#25 lore raw
In reply to Thomas Gleixner
Quoted 6 lines · Thomas Gleixner

Following up on the discussions around the patch Song submitted to 'cure' a iTLB related performance regression, I picked up Song's patch which makes clone_page_tables() more robust by handling unaligned addresses proper and added one which prevents calling into the PTI code when PTI is enabled compile time, but disabled at runtime (command line or CPU not affected).

Quoted 8 lines

There is no point in calling into those PTI functions unconditionally. The resulting page tables are the same before and after the change which makes sense as the code clones the kernel page table into the secondary page table space which is available but not used when PTI is boot time disabled.

But even if it does not do damage today, this could have nasty side effect when the PTI code is changed, extended etc. later.