diff options
Diffstat (limited to 'arch/arm/mm/dump.c')
| -rw-r--r-- | arch/arm/mm/dump.c | 208 |
1 files changed, 146 insertions, 62 deletions
diff --git a/arch/arm/mm/dump.c b/arch/arm/mm/dump.c index 35ff45470dbf..cd032522d902 100644 --- a/arch/arm/mm/dump.c +++ b/arch/arm/mm/dump.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Debug helper to dump the current kernel pagetables of the system * so that we can see what the various memory ranges are set to. @@ -6,11 +7,6 @@ * (C) Copyright 2008 Intel Corporation * * Author: Arjan van de Ven <arjan@linux.intel.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; version 2 - * of the License. */ #include <linux/debugfs.h> #include <linux/fs.h> @@ -19,31 +15,44 @@ #include <asm/domain.h> #include <asm/fixmap.h> -#include <asm/memory.h> -#include <asm/pgtable.h> - -struct addr_marker { - unsigned long start_address; - const char *name; -}; +#include <asm/page.h> +#include <asm/ptdump.h> static struct addr_marker address_markers[] = { +#ifdef CONFIG_KASAN + { KASAN_SHADOW_START, "Kasan shadow start"}, + { KASAN_SHADOW_END, "Kasan shadow end"}, +#endif { MODULES_VADDR, "Modules" }, { PAGE_OFFSET, "Kernel Mapping" }, { 0, "vmalloc() Area" }, - { VMALLOC_END, "vmalloc() End" }, + { FDT_FIXED_BASE, "FDT Area" }, { FIXADDR_START, "Fixmap Area" }, { VECTORS_BASE, "Vectors" }, { VECTORS_BASE + PAGE_SIZE * 2, "Vectors End" }, { -1, NULL }, }; +#define pt_dump_seq_printf(m, fmt, args...) \ +({ \ + if (m) \ + seq_printf(m, fmt, ##args); \ +}) + +#define pt_dump_seq_puts(m, fmt) \ +({ \ + if (m) \ + seq_printf(m, fmt); \ +}) + struct pg_state { struct seq_file *seq; const struct addr_marker *marker; unsigned long start_address; unsigned level; u64 current_prot; + bool check_wx; + unsigned long wx_pages; const char *current_domain; }; @@ -52,6 +61,8 @@ struct prot_bits { u64 val; const char *set; const char *clear; + bool ro_bit; + bool nx_bit; }; static const struct prot_bits pte_bits[] = { @@ -65,11 +76,13 @@ static const struct prot_bits pte_bits[] = { .val = L_PTE_RDONLY, .set = "ro", .clear = "RW", + .ro_bit = true, }, { .mask = L_PTE_XN, .val = L_PTE_XN, .set = "NX", .clear = "x ", + .nx_bit = true, }, { .mask = L_PTE_SHARED, .val = L_PTE_SHARED, @@ -129,15 +142,17 @@ static const struct prot_bits section_bits[] = { .val = PMD_SECT_USER, .set = "USR", }, { - .mask = L_PMD_SECT_RDONLY, - .val = L_PMD_SECT_RDONLY, + .mask = L_PMD_SECT_RDONLY | PMD_SECT_AP2, + .val = L_PMD_SECT_RDONLY | PMD_SECT_AP2, .set = "ro", .clear = "RW", + .ro_bit = true, #elif __LINUX_ARM_ARCH__ >= 6 { .mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, .val = PMD_SECT_APX | PMD_SECT_AP_WRITE, .set = " ro", + .ro_bit = true, }, { .mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, .val = PMD_SECT_AP_WRITE, @@ -156,6 +171,7 @@ static const struct prot_bits section_bits[] = { .mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, .val = 0, .set = " ro", + .ro_bit = true, }, { .mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, .val = PMD_SECT_AP_WRITE, @@ -174,6 +190,7 @@ static const struct prot_bits section_bits[] = { .val = PMD_SECT_XN, .set = "NX", .clear = "x ", + .nx_bit = true, }, { .mask = PMD_SECT_S, .val = PMD_SECT_S, @@ -183,19 +200,25 @@ static const struct prot_bits section_bits[] = { }; struct pg_level { + const char *name; const struct prot_bits *bits; size_t num; u64 mask; + const struct prot_bits *ro_bit; + const struct prot_bits *nx_bit; }; static struct pg_level pg_level[] = { { }, { /* pgd */ + }, { /* p4d */ }, { /* pud */ }, { /* pmd */ + .name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD", .bits = section_bits, .num = ARRAY_SIZE(section_bits), }, { /* pte */ + .name = "PTE", .bits = pte_bits, .num = ARRAY_SIZE(pte_bits), }, @@ -214,10 +237,27 @@ static void dump_prot(struct pg_state *st, const struct prot_bits *bits, size_t s = bits->clear; if (s) - seq_printf(st->seq, " %s", s); + pt_dump_seq_printf(st->seq, " %s", s); } } +static void note_prot_wx(struct pg_state *st, unsigned long addr) +{ + if (!st->check_wx) + return; + if ((st->current_prot & pg_level[st->level].ro_bit->mask) == + pg_level[st->level].ro_bit->val) + return; + if ((st->current_prot & pg_level[st->level].nx_bit->mask) == + pg_level[st->level].nx_bit->val) + return; + + WARN_ONCE(1, "arm/mm: Found insecure W+X mapping at address %pS\n", + (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, const char *domain) { @@ -228,7 +268,7 @@ static void note_page(struct pg_state *st, unsigned long addr, st->level = level; st->current_prot = prot; st->current_domain = domain; - seq_printf(st->seq, "---[ %s ]---\n", st->marker->name); + pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name); } else if (prot != st->current_prot || level != st->level || domain != st->current_domain || addr >= st->marker[1].start_address) { @@ -236,7 +276,8 @@ static void note_page(struct pg_state *st, unsigned long addr, unsigned long delta; if (st->current_prot) { - seq_printf(st->seq, "0x%08lx-0x%08lx ", + note_prot_wx(st, addr); + pt_dump_seq_printf(st->seq, "0x%08lx-0x%08lx ", st->start_address, addr); delta = (addr - st->start_address) >> 10; @@ -244,17 +285,20 @@ static void note_page(struct pg_state *st, unsigned long addr, delta >>= 10; unit++; } - seq_printf(st->seq, "%9lu%c", delta, *unit); + pt_dump_seq_printf(st->seq, "%9lu%c %s", delta, *unit, + pg_level[st->level].name); if (st->current_domain) - seq_printf(st->seq, " %s", st->current_domain); + pt_dump_seq_printf(st->seq, " %s", + st->current_domain); if (pg_level[st->level].bits) dump_prot(st, pg_level[st->level].bits, pg_level[st->level].num); - seq_printf(st->seq, "\n"); + pt_dump_seq_printf(st->seq, "\n"); } if (addr >= st->marker[1].start_address) { st->marker++; - seq_printf(st->seq, "---[ %s ]---\n", st->marker->name); + pt_dump_seq_printf(st->seq, "---[ %s ]---\n", + st->marker->name); } st->start_address = addr; st->current_prot = prot; @@ -272,7 +316,7 @@ static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start, for (i = 0; i < PTRS_PER_PTE; i++, pte++) { addr = start + i * PAGE_SIZE; - note_page(st, addr, 4, pte_val(*pte), domain); + note_page(st, addr, 5, pte_val(*pte), domain); } } @@ -305,23 +349,23 @@ static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start) for (i = 0; i < PTRS_PER_PMD; i++, pmd++) { addr = start + i * PMD_SIZE; domain = get_domain_name(pmd); - if (pmd_none(*pmd) || pmd_large(*pmd) || !pmd_present(*pmd)) - note_page(st, addr, 3, pmd_val(*pmd), domain); + if (pmd_none(*pmd) || pmd_leaf(*pmd) || !pmd_present(*pmd)) + note_page(st, addr, 4, pmd_val(*pmd), domain); else walk_pte(st, pmd, addr, domain); - if (SECTION_SIZE < PMD_SIZE && pmd_large(pmd[1])) { + if (SECTION_SIZE < PMD_SIZE && pmd_leaf(pmd[1])) { addr += SECTION_SIZE; pmd++; domain = get_domain_name(pmd); - note_page(st, addr, 3, pmd_val(*pmd), domain); + note_page(st, addr, 4, pmd_val(*pmd), domain); } } } -static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start) +static void walk_pud(struct pg_state *st, p4d_t *p4d, unsigned long start) { - pud_t *pud = pud_offset(pgd, 0); + pud_t *pud = pud_offset(p4d, 0); unsigned long addr; unsigned i; @@ -330,66 +374,106 @@ static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start) if (!pud_none(*pud)) { walk_pmd(st, pud, addr); } else { - note_page(st, addr, 2, pud_val(*pud), NULL); + note_page(st, addr, 3, pud_val(*pud), NULL); } } } -static void walk_pgd(struct seq_file *m) +static void walk_p4d(struct pg_state *st, pgd_t *pgd, unsigned long start) { - pgd_t *pgd = swapper_pg_dir; - struct pg_state st; + p4d_t *p4d = p4d_offset(pgd, 0); unsigned long addr; unsigned i; - memset(&st, 0, sizeof(st)); - st.seq = m; - st.marker = address_markers; + for (i = 0; i < PTRS_PER_P4D; i++, p4d++) { + addr = start + i * P4D_SIZE; + if (!p4d_none(*p4d)) { + walk_pud(st, p4d, addr); + } else { + note_page(st, addr, 2, p4d_val(*p4d), NULL); + } + } +} + +static void walk_pgd(struct pg_state *st, struct mm_struct *mm, + unsigned long start) +{ + pgd_t *pgd = pgd_offset(mm, 0UL); + unsigned i; + unsigned long addr; for (i = 0; i < PTRS_PER_PGD; i++, pgd++) { - addr = i * PGDIR_SIZE; + addr = start + i * PGDIR_SIZE; if (!pgd_none(*pgd)) { - walk_pud(&st, pgd, addr); + walk_p4d(st, pgd, addr); } else { - note_page(&st, addr, 1, pgd_val(*pgd), NULL); + note_page(st, addr, 1, pgd_val(*pgd), NULL); } } - - note_page(&st, 0, 0, 0, NULL); } -static int ptdump_show(struct seq_file *m, void *v) +void ptdump_walk_pgd(struct seq_file *m, struct ptdump_info *info) { - walk_pgd(m); - return 0; -} + struct pg_state st = { + .seq = m, + .marker = info->markers, + .check_wx = false, + }; -static int ptdump_open(struct inode *inode, struct file *file) -{ - return single_open(file, ptdump_show, NULL); + walk_pgd(&st, info->mm, info->base_addr); + note_page(&st, 0, 0, 0, NULL); } -static const struct file_operations ptdump_fops = { - .open = ptdump_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int ptdump_init(void) +static void __init ptdump_initialize(void) { - struct dentry *pe; unsigned i, j; for (i = 0; i < ARRAY_SIZE(pg_level); i++) if (pg_level[i].bits) - for (j = 0; j < pg_level[i].num; j++) + for (j = 0; j < pg_level[i].num; j++) { pg_level[i].mask |= pg_level[i].bits[j].mask; - + if (pg_level[i].bits[j].ro_bit) + pg_level[i].ro_bit = &pg_level[i].bits[j]; + if (pg_level[i].bits[j].nx_bit) + pg_level[i].nx_bit = &pg_level[i].bits[j]; + } +#ifdef CONFIG_KASAN + address_markers[4].start_address = VMALLOC_START; +#else address_markers[2].start_address = VMALLOC_START; +#endif +} + +static struct ptdump_info kernel_ptdump_info = { + .mm = &init_mm, + .markers = address_markers, + .base_addr = 0, +}; + +void ptdump_check_wx(void) +{ + struct pg_state st = { + .seq = NULL, + .marker = (struct addr_marker[]) { + { 0, NULL}, + { -1, NULL}, + }, + .check_wx = true, + }; + + walk_pgd(&st, &init_mm, 0); + note_page(&st, 0, 0, 0, NULL); + 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"); +} - pe = debugfs_create_file("kernel_page_tables", 0400, NULL, NULL, - &ptdump_fops); - return pe ? 0 : -ENOMEM; +static int __init ptdump_init(void) +{ + ptdump_initialize(); + ptdump_debugfs_register(&kernel_ptdump_info, "kernel_page_tables"); + return 0; } __initcall(ptdump_init); |
