aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell Currey <ruscur@russell.cc>2019-05-02 03:39:47 -0400
committerMichael Ellerman <mpe@ellerman.id.au>2019-05-02 12:54:45 -0400
commit453d87f6a8aed827f5ebb1708a4cea458fd68d23 (patch)
treebc0424487d4f512ca6313ac84b03568784b19965
parent5f18cbdbdd42b050c51eb9859f8ce43db3f51846 (diff)
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: Russell Currey <ruscur@russell.cc> [mpe: Fixup build error when disabled] Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
-rw-r--r--arch/powerpc/Kconfig.debug19
-rw-r--r--arch/powerpc/include/asm/pgtable.h6
-rw-r--r--arch/powerpc/mm/pgtable_32.c3
-rw-r--r--arch/powerpc/mm/pgtable_64.c3
-rw-r--r--arch/powerpc/mm/ptdump/ptdump.c43
5 files changed, 73 insertions, 1 deletions
diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug
index 61febbbdd02b..e9ae650c8e93 100644
--- a/arch/powerpc/Kconfig.debug
+++ b/arch/powerpc/Kconfig.debug
@@ -361,6 +361,25 @@ config PPC_PTDUMP
361 361
362 If you are unsure, say N. 362 If you are unsure, say N.
363 363
364config PPC_DEBUG_WX
365 bool "Warn on W+X mappings at boot"
366 depends on PPC_PTDUMP
367 help
368 Generate a warning if any W+X mappings are found at boot.
369
370 This is useful for discovering cases where the kernel is leaving
371 W+X mappings after applying NX, as such mappings are a security risk.
372
373 Note that even if the check fails, your kernel is possibly
374 still fine, as W+X mappings are not a security hole in
375 themselves, what they do is that they make the exploitation
376 of other unfixed kernel bugs easier.
377
378 There is no runtime or memory usage effect of this option
379 once the kernel has booted up - it's a one time check.
380
381 If in doubt, say "Y".
382
364config PPC_FAST_ENDIAN_SWITCH 383config PPC_FAST_ENDIAN_SWITCH
365 bool "Deprecated fast endian-switch syscall" 384 bool "Deprecated fast endian-switch syscall"
366 depends on DEBUG_KERNEL && PPC_BOOK3S_64 385 depends on DEBUG_KERNEL && PPC_BOOK3S_64
diff --git a/arch/powerpc/include/asm/pgtable.h b/arch/powerpc/include/asm/pgtable.h
index c51846da41a7..3f53be60fb01 100644
--- a/arch/powerpc/include/asm/pgtable.h
+++ b/arch/powerpc/include/asm/pgtable.h
@@ -105,6 +105,12 @@ void mark_initmem_nx(void);
105static inline void mark_initmem_nx(void) { } 105static inline void mark_initmem_nx(void) { }
106#endif 106#endif
107 107
108#ifdef CONFIG_PPC_DEBUG_WX
109void ptdump_check_wx(void);
110#else
111static inline void ptdump_check_wx(void) { }
112#endif
113
108/* 114/*
109 * When used, PTE_FRAG_NR is defined in subarch pgtable.h 115 * When used, PTE_FRAG_NR is defined in subarch pgtable.h
110 * so we are sure it is included when arriving here. 116 * so we are sure it is included when arriving here.
diff --git a/arch/powerpc/mm/pgtable_32.c b/arch/powerpc/mm/pgtable_32.c
index 2e67f9a1430b..16ada373b32b 100644
--- a/arch/powerpc/mm/pgtable_32.c
+++ b/arch/powerpc/mm/pgtable_32.c
@@ -396,6 +396,9 @@ void mark_rodata_ro(void)
396 PFN_DOWN((unsigned long)__start_rodata); 396 PFN_DOWN((unsigned long)__start_rodata);
397 397
398 change_page_attr(page, numpages, PAGE_KERNEL_RO); 398 change_page_attr(page, numpages, PAGE_KERNEL_RO);
399
400 // mark_initmem_nx() should have already run by now
401 ptdump_check_wx();
399} 402}
400#endif 403#endif
401 404
diff --git a/arch/powerpc/mm/pgtable_64.c b/arch/powerpc/mm/pgtable_64.c
index 4c6a73782b19..d2d976ff8a0e 100644
--- a/arch/powerpc/mm/pgtable_64.c
+++ b/arch/powerpc/mm/pgtable_64.c
@@ -332,6 +332,9 @@ void mark_rodata_ro(void)
332 radix__mark_rodata_ro(); 332 radix__mark_rodata_ro();
333 else 333 else
334 hash__mark_rodata_ro(); 334 hash__mark_rodata_ro();
335
336 // mark_initmem_nx() should have already run by now
337 ptdump_check_wx();
335} 338}
336 339
337void mark_initmem_nx(void) 340void mark_initmem_nx(void)
diff --git a/arch/powerpc/mm/ptdump/ptdump.c b/arch/powerpc/mm/ptdump/ptdump.c
index e249a56c07bf..646876d9da64 100644
--- a/arch/powerpc/mm/ptdump/ptdump.c
+++ b/arch/powerpc/mm/ptdump/ptdump.c
@@ -31,7 +31,7 @@
31#include "ptdump.h" 31#include "ptdump.h"
32 32
33#ifdef CONFIG_PPC32 33#ifdef CONFIG_PPC32
34#define KERN_VIRT_START 0 34#define KERN_VIRT_START PAGE_OFFSET
35#endif 35#endif
36 36
37/* 37/*
@@ -68,6 +68,8 @@ struct pg_state {
68 unsigned long last_pa; 68 unsigned long last_pa;
69 unsigned int level; 69 unsigned int level;
70 u64 current_flags; 70 u64 current_flags;
71 bool check_wx;
72 unsigned long wx_pages;
71}; 73};
72 74
73struct addr_marker { 75struct addr_marker {
@@ -181,6 +183,20 @@ static void dump_addr(struct pg_state *st, unsigned long addr)
181 183
182} 184}
183 185
186static void note_prot_wx(struct pg_state *st, unsigned long addr)
187{
188 if (!st->check_wx)
189 return;
190
191 if (!((st->current_flags & pgprot_val(PAGE_KERNEL_X)) == pgprot_val(PAGE_KERNEL_X)))
192 return;
193
194 WARN_ONCE(1, "powerpc/mm: Found insecure W+X mapping at address %p/%pS\n",
195 (void *)st->start_address, (void *)st->start_address);
196
197 st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
198}
199
184static void note_page(struct pg_state *st, unsigned long addr, 200static void note_page(struct pg_state *st, unsigned long addr,
185 unsigned int level, u64 val) 201 unsigned int level, u64 val)
186{ 202{
@@ -210,6 +226,7 @@ static void note_page(struct pg_state *st, unsigned long addr,
210 226
211 /* Check the PTE flags */ 227 /* Check the PTE flags */
212 if (st->current_flags) { 228 if (st->current_flags) {
229 note_prot_wx(st, addr);
213 dump_addr(st, addr); 230 dump_addr(st, addr);
214 231
215 /* Dump all the flags */ 232 /* Dump all the flags */
@@ -387,6 +404,30 @@ static void build_pgtable_complete_mask(void)
387 pg_level[i].mask |= pg_level[i].flag[j].mask; 404 pg_level[i].mask |= pg_level[i].flag[j].mask;
388} 405}
389 406
407#ifdef CONFIG_PPC_DEBUG_WX
408void ptdump_check_wx(void)
409{
410 struct pg_state st = {
411 .seq = NULL,
412 .marker = address_markers,
413 .check_wx = true,
414 };
415
416 if (radix_enabled())
417 st.start_address = PAGE_OFFSET;
418 else
419 st.start_address = KERN_VIRT_START;
420
421 walk_pagetables(&st);
422
423 if (st.wx_pages)
424 pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n",
425 st.wx_pages);
426 else
427 pr_info("Checked W+X mappings: passed, no W+X pages found\n");
428}
429#endif
430
390static int ptdump_init(void) 431static int ptdump_init(void)
391{ 432{
392 struct dentry *debugfs_file; 433 struct dentry *debugfs_file;