Skip to content

Commit 1404d6f

Browse files
labbottctmarinas
authored andcommitted
arm64: dump: Add checking for writable and exectuable pages
Page mappings with full RWX permissions are a security risk. x86 has an option to walk the page tables and dump any bad pages. (See e1a5832 ("x86/mm: Warn on W^X mappings")). Add a similar implementation for arm64. Reviewed-by: Kees Cook <[email protected]> Reviewed-by: Mark Rutland <[email protected]> Tested-by: Mark Rutland <[email protected]> Signed-off-by: Laura Abbott <[email protected]> Reviewed-by: Ard Biesheuvel <[email protected]> [[email protected]: folded fix for KASan out of bounds from Mark Rutland] Signed-off-by: Catalin Marinas <[email protected]>
1 parent cfd69e9 commit 1404d6f

File tree

4 files changed

+94
-0
lines changed

4 files changed

+94
-0
lines changed

arch/arm64/Kconfig.debug

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,35 @@ config ARM64_RANDOMIZE_TEXT_OFFSET
4242
of TEXT_OFFSET and platforms must not require a specific
4343
value.
4444

45+
config DEBUG_WX
46+
bool "Warn on W+X mappings at boot"
47+
select ARM64_PTDUMP_CORE
48+
---help---
49+
Generate a warning if any W+X mappings are found at boot.
50+
51+
This is useful for discovering cases where the kernel is leaving
52+
W+X mappings after applying NX, as such mappings are a security risk.
53+
This check also includes UXN, which should be set on all kernel
54+
mappings.
55+
56+
Look for a message in dmesg output like this:
57+
58+
arm64/mm: Checked W+X mappings: passed, no W+X pages found.
59+
60+
or like this, if the check failed:
61+
62+
arm64/mm: Checked W+X mappings: FAILED, <N> W+X pages found.
63+
64+
Note that even if the check fails, your kernel is possibly
65+
still fine, as W+X mappings are not a security hole in
66+
themselves, what they do is that they make the exploitation
67+
of other unfixed kernel bugs easier.
68+
69+
There is no runtime or memory usage effect of this option
70+
once the kernel has booted up - it's a one time check.
71+
72+
If in doubt, say "Y".
73+
4574
config DEBUG_SET_MODULE_RONX
4675
bool "Set loadable kernel module data as NX and text as RO"
4776
depends on MODULES

arch/arm64/include/asm/ptdump.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,13 @@ static inline int ptdump_debugfs_register(struct ptdump_info *info,
4242
return 0;
4343
}
4444
#endif
45+
void ptdump_check_wx(void);
4546
#endif /* CONFIG_ARM64_PTDUMP_CORE */
47+
48+
#ifdef CONFIG_DEBUG_WX
49+
#define debug_checkwx() ptdump_check_wx()
50+
#else
51+
#define debug_checkwx() do { } while (0)
52+
#endif
53+
4654
#endif /* __ASM_PTDUMP_H */

arch/arm64/mm/dump.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ struct pg_state {
7474
unsigned long start_address;
7575
unsigned level;
7676
u64 current_prot;
77+
bool check_wx;
78+
unsigned long wx_pages;
79+
unsigned long uxn_pages;
7780
};
7881

7982
struct prot_bits {
@@ -202,6 +205,35 @@ static void dump_prot(struct pg_state *st, const struct prot_bits *bits,
202205
}
203206
}
204207

208+
static void note_prot_uxn(struct pg_state *st, unsigned long addr)
209+
{
210+
if (!st->check_wx)
211+
return;
212+
213+
if ((st->current_prot & PTE_UXN) == PTE_UXN)
214+
return;
215+
216+
WARN_ONCE(1, "arm64/mm: Found non-UXN mapping at address %p/%pS\n",
217+
(void *)st->start_address, (void *)st->start_address);
218+
219+
st->uxn_pages += (addr - st->start_address) / PAGE_SIZE;
220+
}
221+
222+
static void note_prot_wx(struct pg_state *st, unsigned long addr)
223+
{
224+
if (!st->check_wx)
225+
return;
226+
if ((st->current_prot & PTE_RDONLY) == PTE_RDONLY)
227+
return;
228+
if ((st->current_prot & PTE_PXN) == PTE_PXN)
229+
return;
230+
231+
WARN_ONCE(1, "arm64/mm: Found insecure W+X mapping at address %p/%pS\n",
232+
(void *)st->start_address, (void *)st->start_address);
233+
234+
st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
235+
}
236+
205237
static void note_page(struct pg_state *st, unsigned long addr, unsigned level,
206238
u64 val)
207239
{
@@ -219,6 +251,8 @@ static void note_page(struct pg_state *st, unsigned long addr, unsigned level,
219251
unsigned long delta;
220252

221253
if (st->current_prot) {
254+
note_prot_uxn(st, addr);
255+
note_prot_wx(st, addr);
222256
pt_dump_seq_printf(st->seq, "0x%016lx-0x%016lx ",
223257
st->start_address, addr);
224258

@@ -344,6 +378,26 @@ static struct ptdump_info kernel_ptdump_info = {
344378
.base_addr = VA_START,
345379
};
346380

381+
void ptdump_check_wx(void)
382+
{
383+
struct pg_state st = {
384+
.seq = NULL,
385+
.marker = (struct addr_marker[]) {
386+
{ 0, NULL},
387+
{ -1, NULL},
388+
},
389+
.check_wx = true,
390+
};
391+
392+
walk_pgd(&st, &init_mm, 0);
393+
note_page(&st, 0, 0, 0);
394+
if (st.wx_pages || st.uxn_pages)
395+
pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
396+
st.wx_pages, st.uxn_pages);
397+
else
398+
pr_info("Checked W+X mappings: passed, no W+X pages found\n");
399+
}
400+
347401
static int ptdump_init(void)
348402
{
349403
ptdump_initialize();

arch/arm64/mm/mmu.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <asm/tlb.h>
4141
#include <asm/memblock.h>
4242
#include <asm/mmu_context.h>
43+
#include <asm/ptdump.h>
4344

4445
u64 idmap_t0sz = TCR_T0SZ(VA_BITS);
4546

@@ -438,6 +439,8 @@ void mark_rodata_ro(void)
438439

439440
/* flush the TLBs after updating live kernel mappings */
440441
flush_tlb_all();
442+
443+
debug_checkwx();
441444
}
442445

443446
static void __init map_kernel_segment(pgd_t *pgd, void *va_start, void *va_end,

0 commit comments

Comments
 (0)