212 lines
7.5 KiB
Diff
212 lines
7.5 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Thomas Gleixner <tglx@linutronix.de>
|
|
Date: Mon, 17 Sep 2018 16:29:09 +0200
|
|
Subject: [PATCH] x86/mm/cpa: Rework static_protections()
|
|
|
|
static_protections() is pretty unreadable. Split it up into separate checks
|
|
for each protection area.
|
|
|
|
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
|
|
Reviewed-by: Dave Hansen <dave.hansen@intel.com>
|
|
Cc: Peter Zijlstra <peterz@infradead.org>
|
|
Cc: Bin Yang <bin.yang@intel.com>
|
|
Cc: Mark Gross <mark.gross@intel.com>
|
|
Link: https://lkml.kernel.org/r/20180917143545.913005317@linutronix.de
|
|
|
|
Cc: Zhang Ning <ning.a.zhang@intel.com>
|
|
Signed-off-by: Lili Li <lili.li@intel.com>
|
|
---
|
|
arch/x86/mm/pageattr.c | 162 +++++++++++++++++++++++++----------------
|
|
1 file changed, 98 insertions(+), 64 deletions(-)
|
|
|
|
diff --git a/arch/x86/mm/pageattr.c b/arch/x86/mm/pageattr.c
|
|
index c9f98fdf1310..44964ceaea96 100644
|
|
--- a/arch/x86/mm/pageattr.c
|
|
+++ b/arch/x86/mm/pageattr.c
|
|
@@ -286,84 +286,118 @@ static void cpa_flush_array(unsigned long *start, int numpages, int cache,
|
|
}
|
|
}
|
|
|
|
+#ifdef CONFIG_PCI_BIOS
|
|
/*
|
|
- * Certain areas of memory on x86 require very specific protection flags,
|
|
- * for example the BIOS area or kernel text. Callers don't always get this
|
|
- * right (again, ioremap() on BIOS memory is not uncommon) so this function
|
|
- * checks and fixes these known static required protection bits.
|
|
+ * The BIOS area between 640k and 1Mb needs to be executable for PCI BIOS
|
|
+ * based config access (CONFIG_PCI_GOBIOS) support.
|
|
*/
|
|
-static inline pgprot_t static_protections(pgprot_t prot, unsigned long address,
|
|
- unsigned long pfn)
|
|
-{
|
|
- pgprot_t forbidden = __pgprot(0);
|
|
+#define BIOS_PFN PFN_DOWN(BIOS_BEGIN)
|
|
+#define BIOS_PFN_END PFN_DOWN(BIOS_END)
|
|
|
|
- /*
|
|
- * The BIOS area between 640k and 1Mb needs to be executable for
|
|
- * PCI BIOS based config access (CONFIG_PCI_GOBIOS) support.
|
|
- */
|
|
-#ifdef CONFIG_PCI_BIOS
|
|
- if (pcibios_enabled && within(pfn, BIOS_BEGIN >> PAGE_SHIFT, BIOS_END >> PAGE_SHIFT))
|
|
- pgprot_val(forbidden) |= _PAGE_NX;
|
|
+static pgprotval_t protect_pci_bios(unsigned long pfn)
|
|
+{
|
|
+ if (pcibios_enabled && within(pfn, BIOS_PFN, BIOS_PFN_END))
|
|
+ return _PAGE_NX;
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+static pgprotval_t protect_pci_bios(unsigned long pfn)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
#endif
|
|
|
|
- /*
|
|
- * The kernel text needs to be executable for obvious reasons
|
|
- * Does not cover __inittext since that is gone later on. On
|
|
- * 64bit we do not enforce !NX on the low mapping
|
|
- */
|
|
- if (within(address, (unsigned long)_text, (unsigned long)_etext))
|
|
- pgprot_val(forbidden) |= _PAGE_NX;
|
|
+/*
|
|
+ * The .rodata section needs to be read-only. Using the pfn catches all
|
|
+ * aliases. This also includes __ro_after_init, so do not enforce until
|
|
+ * kernel_set_to_readonly is true.
|
|
+ */
|
|
+static pgprotval_t protect_rodata(unsigned long pfn)
|
|
+{
|
|
+ unsigned long start_pfn = __pa_symbol(__start_rodata) >> PAGE_SHIFT;
|
|
+ unsigned long end_pfn = __pa_symbol(__end_rodata) >> PAGE_SHIFT;
|
|
|
|
- /*
|
|
- * The .rodata section needs to be read-only. Using the pfn
|
|
- * catches all aliases. This also includes __ro_after_init,
|
|
- * so do not enforce until kernel_set_to_readonly is true.
|
|
- */
|
|
- if (kernel_set_to_readonly &&
|
|
- within(pfn, __pa_symbol(__start_rodata) >> PAGE_SHIFT,
|
|
- __pa_symbol(__end_rodata) >> PAGE_SHIFT))
|
|
- pgprot_val(forbidden) |= _PAGE_RW;
|
|
+ if (kernel_set_to_readonly && within(pfn, start_pfn, end_pfn))
|
|
+ return _PAGE_RW;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Protect kernel text against becoming non executable by forbidding
|
|
+ * _PAGE_NX. This protects only the high kernel mapping (_text -> _etext)
|
|
+ * out of which the kernel actually executes. Do not protect the low
|
|
+ * mapping.
|
|
+ *
|
|
+ * This does not cover __inittext since that is gone after boot.
|
|
+ */
|
|
+static pgprotval_t protect_kernel_text(unsigned long address)
|
|
+{
|
|
+ if (within(address, (unsigned long)_text, (unsigned long)_etext))
|
|
+ return _PAGE_NX;
|
|
+ return 0;
|
|
+}
|
|
|
|
#if defined(CONFIG_X86_64)
|
|
+/*
|
|
+ * Once the kernel maps the text as RO (kernel_set_to_readonly is set),
|
|
+ * kernel text mappings for the large page aligned text, rodata sections
|
|
+ * will be always read-only. For the kernel identity mappings covering the
|
|
+ * holes caused by this alignment can be anything that user asks.
|
|
+ *
|
|
+ * This will preserve the large page mappings for kernel text/data at no
|
|
+ * extra cost.
|
|
+ */
|
|
+static pgprotval_t protect_kernel_text_ro(unsigned long address)
|
|
+{
|
|
+ unsigned long end = (unsigned long)__end_rodata_hpage_align;
|
|
+ unsigned long start = (unsigned long)_text;
|
|
+ unsigned int level;
|
|
+
|
|
+ if (!kernel_set_to_readonly || !within(address, start, end))
|
|
+ return 0;
|
|
/*
|
|
- * Once the kernel maps the text as RO (kernel_set_to_readonly is set),
|
|
- * kernel text mappings for the large page aligned text, rodata sections
|
|
- * will be always read-only. For the kernel identity mappings covering
|
|
- * the holes caused by this alignment can be anything that user asks.
|
|
+ * Don't enforce the !RW mapping for the kernel text mapping, if
|
|
+ * the current mapping is already using small page mapping. No
|
|
+ * need to work hard to preserve large page mappings in this case.
|
|
*
|
|
- * This will preserve the large page mappings for kernel text/data
|
|
- * at no extra cost.
|
|
+ * This also fixes the Linux Xen paravirt guest boot failure caused
|
|
+ * by unexpected read-only mappings for kernel identity
|
|
+ * mappings. In this paravirt guest case, the kernel text mapping
|
|
+ * and the kernel identity mapping share the same page-table pages,
|
|
+ * so the protections for kernel text and identity mappings have to
|
|
+ * be the same.
|
|
*/
|
|
- if (kernel_set_to_readonly &&
|
|
- within(address, (unsigned long)_text,
|
|
- (unsigned long)__end_rodata_hpage_align)) {
|
|
- unsigned int level;
|
|
-
|
|
- /*
|
|
- * Don't enforce the !RW mapping for the kernel text mapping,
|
|
- * if the current mapping is already using small page mapping.
|
|
- * No need to work hard to preserve large page mappings in this
|
|
- * case.
|
|
- *
|
|
- * This also fixes the Linux Xen paravirt guest boot failure
|
|
- * (because of unexpected read-only mappings for kernel identity
|
|
- * mappings). In this paravirt guest case, the kernel text
|
|
- * mapping and the kernel identity mapping share the same
|
|
- * page-table pages. Thus we can't really use different
|
|
- * protections for the kernel text and identity mappings. Also,
|
|
- * these shared mappings are made of small page mappings.
|
|
- * Thus this don't enforce !RW mapping for small page kernel
|
|
- * text mapping logic will help Linux Xen parvirt guest boot
|
|
- * as well.
|
|
- */
|
|
- if (lookup_address(address, &level) && (level != PG_LEVEL_4K))
|
|
- pgprot_val(forbidden) |= _PAGE_RW;
|
|
- }
|
|
+ if (lookup_address(address, &level) && (level != PG_LEVEL_4K))
|
|
+ return _PAGE_RW;
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+static pgprotval_t protect_kernel_text_ro(unsigned long address)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
#endif
|
|
|
|
- prot = __pgprot(pgprot_val(prot) & ~pgprot_val(forbidden));
|
|
+/*
|
|
+ * Certain areas of memory on x86 require very specific protection flags,
|
|
+ * for example the BIOS area or kernel text. Callers don't always get this
|
|
+ * right (again, ioremap() on BIOS memory is not uncommon) so this function
|
|
+ * checks and fixes these known static required protection bits.
|
|
+ */
|
|
+static inline pgprot_t static_protections(pgprot_t prot, unsigned long address,
|
|
+ unsigned long pfn)
|
|
+{
|
|
+ pgprotval_t forbidden;
|
|
+
|
|
+ /* Operate on the virtual address */
|
|
+ forbidden = protect_kernel_text(address);
|
|
+ forbidden |= protect_kernel_text_ro(address);
|
|
|
|
- return prot;
|
|
+ /* Check the PFN directly */
|
|
+ forbidden |= protect_pci_bios(pfn);
|
|
+ forbidden |= protect_rodata(pfn);
|
|
+
|
|
+ return __pgprot(pgprot_val(prot) & ~forbidden);
|
|
}
|
|
|
|
/*
|
|
--
|
|
https://clearlinux.org
|
|
|