x86/mm: Refuse W^X violations
authorPeter Zijlstra <peterz@infradead.org>
Mon, 29 Aug 2022 10:18:03 +0000 (12:18 +0200)
committerDave Hansen <dave.hansen@linux.intel.com>
Thu, 1 Sep 2022 18:10:19 +0000 (11:10 -0700)
x86 has STRICT_*_RWX, but not even a warning when someone violates it.

Add this warning and fully refuse the transition.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Link: https://lkml.kernel.org/r/YwySW3ROc21hN7g9@hirez.programming.kicks-ass.net
arch/x86/mm/pat/set_memory.c

index 6a9043b..1a2d637 100644 (file)
@@ -580,6 +580,33 @@ static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
 }
 
 /*
+ * Validate and enforce strict W^X semantics.
+ */
+static inline pgprot_t verify_rwx(pgprot_t old, pgprot_t new, unsigned long start,
+                                 unsigned long pfn, unsigned long npg)
+{
+       unsigned long end;
+
+       if (!cpu_feature_enabled(X86_FEATURE_NX))
+               return new;
+
+       if (!((pgprot_val(old) ^ pgprot_val(new)) & (_PAGE_RW | _PAGE_NX)))
+               return new;
+
+       if ((pgprot_val(new) & (_PAGE_RW | _PAGE_NX)) != _PAGE_RW)
+               return new;
+
+       end = start + npg * PAGE_SIZE - 1;
+       WARN_ONCE(1, "CPA refuse W^X violation: %016llx -> %016llx range: 0x%016lx - 0x%016lx PFN %lx\n",
+                 (unsigned long long)pgprot_val(old),
+                 (unsigned long long)pgprot_val(new),
+                 start, end, pfn);
+
+       /* refuse the transition into WX */
+       return old;
+}
+
+/*
  * Lookup the page table entry for a virtual address in a specific pgd.
  * Return a pointer to the entry and the level of the mapping.
  */
@@ -885,6 +912,8 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
        new_prot = static_protections(req_prot, lpaddr, old_pfn, numpages,
                                      psize, CPA_DETECT);
 
+       new_prot = verify_rwx(old_prot, new_prot, lpaddr, old_pfn, numpages);
+
        /*
         * If there is a conflict, split the large page.
         *
@@ -1525,6 +1554,7 @@ repeat:
 
        if (level == PG_LEVEL_4K) {
                pte_t new_pte;
+               pgprot_t old_prot = pte_pgprot(old_pte);
                pgprot_t new_prot = pte_pgprot(old_pte);
                unsigned long pfn = pte_pfn(old_pte);
 
@@ -1536,6 +1566,8 @@ repeat:
                new_prot = static_protections(new_prot, address, pfn, 1, 0,
                                              CPA_PROTECT);
 
+               new_prot = verify_rwx(old_prot, new_prot, address, pfn, 1);
+
                new_prot = pgprot_clear_protnone_bits(new_prot);
 
                /*