Commit 453d87f6 authored by Russell Currey's avatar Russell Currey Committed by Michael Ellerman

powerpc/mm: Warn if W+X pages found on boot

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: default avatarRussell Currey <ruscur@russell.cc>
[mpe: Fixup build error when disabled]
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
parent 5f18cbdb
...@@ -361,6 +361,25 @@ config PPC_PTDUMP ...@@ -361,6 +361,25 @@ config PPC_PTDUMP
If you are unsure, say N. 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 config PPC_FAST_ENDIAN_SWITCH
bool "Deprecated fast endian-switch syscall" bool "Deprecated fast endian-switch syscall"
depends on DEBUG_KERNEL && PPC_BOOK3S_64 depends on DEBUG_KERNEL && PPC_BOOK3S_64
......
...@@ -105,6 +105,12 @@ void mark_initmem_nx(void); ...@@ -105,6 +105,12 @@ void mark_initmem_nx(void);
static inline void mark_initmem_nx(void) { } static inline void mark_initmem_nx(void) { }
#endif #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 * When used, PTE_FRAG_NR is defined in subarch pgtable.h
* so we are sure it is included when arriving here. * so we are sure it is included when arriving here.
......
...@@ -396,6 +396,9 @@ void mark_rodata_ro(void) ...@@ -396,6 +396,9 @@ void mark_rodata_ro(void)
PFN_DOWN((unsigned long)__start_rodata); PFN_DOWN((unsigned long)__start_rodata);
change_page_attr(page, numpages, PAGE_KERNEL_RO); change_page_attr(page, numpages, PAGE_KERNEL_RO);
// mark_initmem_nx() should have already run by now
ptdump_check_wx();
} }
#endif #endif
......
...@@ -332,6 +332,9 @@ void mark_rodata_ro(void) ...@@ -332,6 +332,9 @@ void mark_rodata_ro(void)
radix__mark_rodata_ro(); radix__mark_rodata_ro();
else else
hash__mark_rodata_ro(); hash__mark_rodata_ro();
// mark_initmem_nx() should have already run by now
ptdump_check_wx();
} }
void mark_initmem_nx(void) void mark_initmem_nx(void)
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
#include "ptdump.h" #include "ptdump.h"
#ifdef CONFIG_PPC32 #ifdef CONFIG_PPC32
#define KERN_VIRT_START 0 #define KERN_VIRT_START PAGE_OFFSET
#endif #endif
/* /*
...@@ -68,6 +68,8 @@ struct pg_state { ...@@ -68,6 +68,8 @@ struct pg_state {
unsigned long last_pa; unsigned long last_pa;
unsigned int level; unsigned int level;
u64 current_flags; u64 current_flags;
bool check_wx;
unsigned long wx_pages;
}; };
struct addr_marker { struct addr_marker {
...@@ -181,6 +183,20 @@ static void dump_addr(struct pg_state *st, unsigned long addr) ...@@ -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, static void note_page(struct pg_state *st, unsigned long addr,
unsigned int level, u64 val) unsigned int level, u64 val)
{ {
...@@ -210,6 +226,7 @@ static void note_page(struct pg_state *st, unsigned long addr, ...@@ -210,6 +226,7 @@ static void note_page(struct pg_state *st, unsigned long addr,
/* Check the PTE flags */ /* Check the PTE flags */
if (st->current_flags) { if (st->current_flags) {
note_prot_wx(st, addr);
dump_addr(st, addr); dump_addr(st, addr);
/* Dump all the flags */ /* Dump all the flags */
...@@ -387,6 +404,30 @@ static void build_pgtable_complete_mask(void) ...@@ -387,6 +404,30 @@ static void build_pgtable_complete_mask(void)
pg_level[i].mask |= pg_level[i].flag[j].mask; 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) static int ptdump_init(void)
{ {
struct dentry *debugfs_file; struct dentry *debugfs_file;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment