powerpc/mm: Warn if W+X pages found on boot
authorRussell Currey <ruscur@russell.cc>
Thu, 2 May 2019 07:39:47 +0000 (17:39 +1000)
committerMichael Ellerman <mpe@ellerman.id.au>
Thu, 2 May 2019 16:54:45 +0000 (02:54 +1000)
Implement code to walk all pages and warn if any are found to be both
writable and executable.  Depends on STRICT_KERNEL_RWX enabled, and is
behind the DEBUG_WX config option.

This only runs on boot and has no runtime performance implications.

Very heavily influenced (and in some cases copied verbatim) from the
ARM64 code written by Laura Abbott (thanks!), since our ptdump
infrastructure is similar.

Signed-off-by: Russell Currey <ruscur@russell.cc>
[mpe: Fixup build error when disabled]
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
arch/powerpc/Kconfig.debug
arch/powerpc/include/asm/pgtable.h
arch/powerpc/mm/pgtable_32.c
arch/powerpc/mm/pgtable_64.c
arch/powerpc/mm/ptdump/ptdump.c

index 61febbbdd02b357b9358d9afe3bdb8c669b76025..e9ae650c8e938f60262ce8d2023be8e5ecf89f33 100644 (file)
@@ -361,6 +361,25 @@ config PPC_PTDUMP
 
          If you are unsure, say N.
 
+config PPC_DEBUG_WX
+       bool "Warn on W+X mappings at boot"
+       depends on PPC_PTDUMP
+       help
+         Generate a warning if any W+X mappings are found at boot.
+
+         This is useful for discovering cases where the kernel is leaving
+         W+X mappings after applying NX, as such mappings are a security risk.
+
+         Note that even if the check fails, your kernel is possibly
+         still fine, as W+X mappings are not a security hole in
+         themselves, what they do is that they make the exploitation
+         of other unfixed kernel bugs easier.
+
+         There is no runtime or memory usage effect of this option
+         once the kernel has booted up - it's a one time check.
+
+         If in doubt, say "Y".
+
 config PPC_FAST_ENDIAN_SWITCH
        bool "Deprecated fast endian-switch syscall"
         depends on DEBUG_KERNEL && PPC_BOOK3S_64
index c51846da41a77148e2d70c347abc78724d11c490..3f53be60fb014928440ba5e3f5e7e9a985b9499e 100644 (file)
@@ -105,6 +105,12 @@ void mark_initmem_nx(void);
 static inline void mark_initmem_nx(void) { }
 #endif
 
+#ifdef CONFIG_PPC_DEBUG_WX
+void ptdump_check_wx(void);
+#else
+static inline void ptdump_check_wx(void) { }
+#endif
+
 /*
  * When used, PTE_FRAG_NR is defined in subarch pgtable.h
  * so we are sure it is included when arriving here.
index 2e67f9a1430b3b797ec0acc6499a062b8cf3b58f..16ada373b32b5144d2e94941d56d9c3a07bf2f6c 100644 (file)
@@ -396,6 +396,9 @@ void mark_rodata_ro(void)
                   PFN_DOWN((unsigned long)__start_rodata);
 
        change_page_attr(page, numpages, PAGE_KERNEL_RO);
+
+       // mark_initmem_nx() should have already run by now
+       ptdump_check_wx();
 }
 #endif
 
index 4c6a73782b19f2df23df11f01299a58d128dec27..d2d976ff8a0e2f2c33004229bef390ea65e3a20c 100644 (file)
@@ -332,6 +332,9 @@ void mark_rodata_ro(void)
                radix__mark_rodata_ro();
        else
                hash__mark_rodata_ro();
+
+       // mark_initmem_nx() should have already run by now
+       ptdump_check_wx();
 }
 
 void mark_initmem_nx(void)
index e249a56c07bfb3e92502dad59ef34e8ecb55c882..646876d9da640fac314b7f1120af7f368439b588 100644 (file)
@@ -31,7 +31,7 @@
 #include "ptdump.h"
 
 #ifdef CONFIG_PPC32
-#define KERN_VIRT_START        0
+#define KERN_VIRT_START        PAGE_OFFSET
 #endif
 
 /*
@@ -68,6 +68,8 @@ struct pg_state {
        unsigned long last_pa;
        unsigned int level;
        u64 current_flags;
+       bool check_wx;
+       unsigned long wx_pages;
 };
 
 struct addr_marker {
@@ -181,6 +183,20 @@ static void dump_addr(struct pg_state *st, unsigned long addr)
 
 }
 
+static void note_prot_wx(struct pg_state *st, unsigned long addr)
+{
+       if (!st->check_wx)
+               return;
+
+       if (!((st->current_flags & pgprot_val(PAGE_KERNEL_X)) == pgprot_val(PAGE_KERNEL_X)))
+               return;
+
+       WARN_ONCE(1, "powerpc/mm: Found insecure W+X mapping at address %p/%pS\n",
+                 (void *)st->start_address, (void *)st->start_address);
+
+       st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
+}
+
 static void note_page(struct pg_state *st, unsigned long addr,
               unsigned int level, u64 val)
 {
@@ -210,6 +226,7 @@ static void note_page(struct pg_state *st, unsigned long addr,
 
                /* Check the PTE flags */
                if (st->current_flags) {
+                       note_prot_wx(st, addr);
                        dump_addr(st, addr);
 
                        /* Dump all the flags */
@@ -387,6 +404,30 @@ static void build_pgtable_complete_mask(void)
                                pg_level[i].mask |= pg_level[i].flag[j].mask;
 }
 
+#ifdef CONFIG_PPC_DEBUG_WX
+void ptdump_check_wx(void)
+{
+       struct pg_state st = {
+               .seq = NULL,
+               .marker = address_markers,
+               .check_wx = true,
+       };
+
+       if (radix_enabled())
+               st.start_address = PAGE_OFFSET;
+       else
+               st.start_address = KERN_VIRT_START;
+
+       walk_pagetables(&st);
+
+       if (st.wx_pages)
+               pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n",
+                       st.wx_pages);
+       else
+               pr_info("Checked W+X mappings: passed, no W+X pages found\n");
+}
+#endif
+
 static int ptdump_init(void)
 {
        struct dentry *debugfs_file;