1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 2da1694adSHeiko Carstens #include <linux/set_memory.h> 39d719d39SHeiko Carstens #include <linux/ptdump.h> 4e76e82d7SHeiko Carstens #include <linux/seq_file.h> 5e76e82d7SHeiko Carstens #include <linux/debugfs.h> 6e76e82d7SHeiko Carstens #include <linux/mm.h> 7e006222bSVasily Gorbik #include <linux/kasan.h> 808c8e685SHeiko Carstens #include <asm/ptdump.h> 90dac8f6bSVasily Gorbik #include <asm/kasan.h> 10e76e82d7SHeiko Carstens #include <asm/sections.h> 11e76e82d7SHeiko Carstens 12e76e82d7SHeiko Carstens static unsigned long max_addr; 13e76e82d7SHeiko Carstens 14e76e82d7SHeiko Carstens struct addr_marker { 15e76e82d7SHeiko Carstens unsigned long start_address; 16e76e82d7SHeiko Carstens const char *name; 17e76e82d7SHeiko Carstens }; 18e76e82d7SHeiko Carstens 19e76e82d7SHeiko Carstens enum address_markers_idx { 20e670e64aSVasily Gorbik IDENTITY_BEFORE_NR = 0, 21e670e64aSVasily Gorbik IDENTITY_BEFORE_END_NR, 22e76e82d7SHeiko Carstens KERNEL_START_NR, 23e76e82d7SHeiko Carstens KERNEL_END_NR, 24e670e64aSVasily Gorbik IDENTITY_AFTER_NR, 25e670e64aSVasily Gorbik IDENTITY_AFTER_END_NR, 260dac8f6bSVasily Gorbik #ifdef CONFIG_KASAN 270dac8f6bSVasily Gorbik KASAN_SHADOW_START_NR, 280dac8f6bSVasily Gorbik KASAN_SHADOW_END_NR, 290dac8f6bSVasily Gorbik #endif 30e76e82d7SHeiko Carstens VMEMMAP_NR, 31e670e64aSVasily Gorbik VMEMMAP_END_NR, 32e76e82d7SHeiko Carstens VMALLOC_NR, 33e670e64aSVasily Gorbik VMALLOC_END_NR, 34c972cc60SHeiko Carstens MODULES_NR, 35e670e64aSVasily Gorbik MODULES_END_NR, 36e76e82d7SHeiko Carstens }; 37e76e82d7SHeiko Carstens 38e76e82d7SHeiko Carstens static struct addr_marker address_markers[] = { 39e670e64aSVasily Gorbik [IDENTITY_BEFORE_NR] = {0, "Identity Mapping Start"}, 40e670e64aSVasily Gorbik [IDENTITY_BEFORE_END_NR] = {(unsigned long)_stext, "Identity Mapping End"}, 41320d9555SVasily Gorbik [KERNEL_START_NR] = {(unsigned long)_stext, "Kernel Image Start"}, 42320d9555SVasily Gorbik [KERNEL_END_NR] = {(unsigned long)_end, "Kernel Image End"}, 43e670e64aSVasily Gorbik [IDENTITY_AFTER_NR] = {(unsigned long)_end, "Identity Mapping Start"}, 44e670e64aSVasily Gorbik [IDENTITY_AFTER_END_NR] = {0, "Identity Mapping End"}, 450dac8f6bSVasily Gorbik #ifdef CONFIG_KASAN 460dac8f6bSVasily Gorbik [KASAN_SHADOW_START_NR] = {KASAN_SHADOW_START, "Kasan Shadow Start"}, 470dac8f6bSVasily Gorbik [KASAN_SHADOW_END_NR] = {KASAN_SHADOW_END, "Kasan Shadow End"}, 480dac8f6bSVasily Gorbik #endif 49e670e64aSVasily Gorbik [VMEMMAP_NR] = {0, "vmemmap Area Start"}, 50e670e64aSVasily Gorbik [VMEMMAP_END_NR] = {0, "vmemmap Area End"}, 51e670e64aSVasily Gorbik [VMALLOC_NR] = {0, "vmalloc Area Start"}, 52e670e64aSVasily Gorbik [VMALLOC_END_NR] = {0, "vmalloc Area End"}, 53e670e64aSVasily Gorbik [MODULES_NR] = {0, "Modules Area Start"}, 54e670e64aSVasily Gorbik [MODULES_END_NR] = {0, "Modules Area End"}, 55e76e82d7SHeiko Carstens { -1, NULL } 56e76e82d7SHeiko Carstens }; 57e76e82d7SHeiko Carstens 58e76e82d7SHeiko Carstens struct pg_state { 599d719d39SHeiko Carstens struct ptdump_state ptdump; 609d719d39SHeiko Carstens struct seq_file *seq; 61e76e82d7SHeiko Carstens int level; 62e76e82d7SHeiko Carstens unsigned int current_prot; 6308c8e685SHeiko Carstens bool check_wx; 6408c8e685SHeiko Carstens unsigned long wx_pages; 65e76e82d7SHeiko Carstens unsigned long start_address; 66e76e82d7SHeiko Carstens const struct addr_marker *marker; 67e76e82d7SHeiko Carstens }; 68e76e82d7SHeiko Carstens 696bf9a639SHeiko Carstens #define pt_dump_seq_printf(m, fmt, args...) \ 706bf9a639SHeiko Carstens ({ \ 716bf9a639SHeiko Carstens struct seq_file *__m = (m); \ 726bf9a639SHeiko Carstens \ 736bf9a639SHeiko Carstens if (__m) \ 746bf9a639SHeiko Carstens seq_printf(__m, fmt, ##args); \ 756bf9a639SHeiko Carstens }) 766bf9a639SHeiko Carstens 776bf9a639SHeiko Carstens #define pt_dump_seq_puts(m, fmt) \ 786bf9a639SHeiko Carstens ({ \ 796bf9a639SHeiko Carstens struct seq_file *__m = (m); \ 806bf9a639SHeiko Carstens \ 816bf9a639SHeiko Carstens if (__m) \ 826bf9a639SHeiko Carstens seq_printf(__m, fmt); \ 836bf9a639SHeiko Carstens }) 846bf9a639SHeiko Carstens 85e76e82d7SHeiko Carstens static void print_prot(struct seq_file *m, unsigned int pr, int level) 86e76e82d7SHeiko Carstens { 87e76e82d7SHeiko Carstens static const char * const level_name[] = 88e76e82d7SHeiko Carstens { "ASCE", "PGD", "PUD", "PMD", "PTE" }; 89e76e82d7SHeiko Carstens 906bf9a639SHeiko Carstens pt_dump_seq_printf(m, "%s ", level_name[level]); 911819ed1fSHeiko Carstens if (pr & _PAGE_INVALID) { 926bf9a639SHeiko Carstens pt_dump_seq_printf(m, "I\n"); 931819ed1fSHeiko Carstens return; 941819ed1fSHeiko Carstens } 956bf9a639SHeiko Carstens pt_dump_seq_puts(m, (pr & _PAGE_PROTECT) ? "RO " : "RW "); 966bf9a639SHeiko Carstens pt_dump_seq_puts(m, (pr & _PAGE_NOEXEC) ? "NX\n" : "X\n"); 97e76e82d7SHeiko Carstens } 98e76e82d7SHeiko Carstens 9908c8e685SHeiko Carstens static void note_prot_wx(struct pg_state *st, unsigned long addr) 10008c8e685SHeiko Carstens { 10108c8e685SHeiko Carstens #ifdef CONFIG_DEBUG_WX 10208c8e685SHeiko Carstens if (!st->check_wx) 10308c8e685SHeiko Carstens return; 10408c8e685SHeiko Carstens if (st->current_prot & _PAGE_INVALID) 10508c8e685SHeiko Carstens return; 10608c8e685SHeiko Carstens if (st->current_prot & _PAGE_PROTECT) 10708c8e685SHeiko Carstens return; 10808c8e685SHeiko Carstens if (st->current_prot & _PAGE_NOEXEC) 10908c8e685SHeiko Carstens return; 11008c8e685SHeiko Carstens /* The first lowcore page is currently still W+X. */ 11108c8e685SHeiko Carstens if (addr == PAGE_SIZE) 11208c8e685SHeiko Carstens return; 11308c8e685SHeiko Carstens WARN_ONCE(1, "s390/mm: Found insecure W+X mapping at address %pS\n", 11408c8e685SHeiko Carstens (void *)st->start_address); 11508c8e685SHeiko Carstens st->wx_pages += (addr - st->start_address) / PAGE_SIZE; 11608c8e685SHeiko Carstens #endif /* CONFIG_DEBUG_WX */ 11708c8e685SHeiko Carstens } 11808c8e685SHeiko Carstens 1199d719d39SHeiko Carstens static void note_page(struct ptdump_state *pt_st, unsigned long addr, int level, u64 val) 120e76e82d7SHeiko Carstens { 121e76e82d7SHeiko Carstens int width = sizeof(unsigned long) * 2; 1229d719d39SHeiko Carstens static const char units[] = "KMGTPE"; 123e76e82d7SHeiko Carstens const char *unit = units; 124e76e82d7SHeiko Carstens unsigned long delta; 1259d719d39SHeiko Carstens struct pg_state *st; 1269d719d39SHeiko Carstens struct seq_file *m; 1279d719d39SHeiko Carstens unsigned int prot; 128e76e82d7SHeiko Carstens 1299d719d39SHeiko Carstens st = container_of(pt_st, struct pg_state, ptdump); 1309d719d39SHeiko Carstens m = st->seq; 1319d719d39SHeiko Carstens prot = val & (_PAGE_PROTECT | _PAGE_NOEXEC); 1329d719d39SHeiko Carstens if (level == 4 && (val & _PAGE_INVALID)) 1339d719d39SHeiko Carstens prot = _PAGE_INVALID; 1349d719d39SHeiko Carstens /* For pmd_none() & friends val gets passed as zero. */ 1359d719d39SHeiko Carstens if (level != 4 && !val) 1369d719d39SHeiko Carstens prot = _PAGE_INVALID; 1379d719d39SHeiko Carstens /* Final flush from generic code. */ 1389d719d39SHeiko Carstens if (level == -1) 1399d719d39SHeiko Carstens addr = max_addr; 1409d719d39SHeiko Carstens if (st->level == -1) { 1416bf9a639SHeiko Carstens pt_dump_seq_printf(m, "---[ %s ]---\n", st->marker->name); 1429d719d39SHeiko Carstens st->start_address = addr; 1439d719d39SHeiko Carstens st->current_prot = prot; 1449d719d39SHeiko Carstens st->level = level; 1459d719d39SHeiko Carstens } else if (prot != st->current_prot || level != st->level || 1469d719d39SHeiko Carstens addr >= st->marker[1].start_address) { 14708c8e685SHeiko Carstens note_prot_wx(st, addr); 1486bf9a639SHeiko Carstens pt_dump_seq_printf(m, "0x%0*lx-0x%0*lx ", 149e76e82d7SHeiko Carstens width, st->start_address, 1509d719d39SHeiko Carstens width, addr); 1519d719d39SHeiko Carstens delta = (addr - st->start_address) >> 10; 152e76e82d7SHeiko Carstens while (!(delta & 0x3ff) && unit[1]) { 153e76e82d7SHeiko Carstens delta >>= 10; 154e76e82d7SHeiko Carstens unit++; 155e76e82d7SHeiko Carstens } 1566bf9a639SHeiko Carstens pt_dump_seq_printf(m, "%9lu%c ", delta, *unit); 157e76e82d7SHeiko Carstens print_prot(m, st->current_prot, st->level); 1589d719d39SHeiko Carstens while (addr >= st->marker[1].start_address) { 159e76e82d7SHeiko Carstens st->marker++; 1606bf9a639SHeiko Carstens pt_dump_seq_printf(m, "---[ %s ]---\n", st->marker->name); 161e76e82d7SHeiko Carstens } 1629d719d39SHeiko Carstens st->start_address = addr; 1639d719d39SHeiko Carstens st->current_prot = prot; 164e76e82d7SHeiko Carstens st->level = level; 165e76e82d7SHeiko Carstens } 166e76e82d7SHeiko Carstens } 167e76e82d7SHeiko Carstens 16808c8e685SHeiko Carstens #ifdef CONFIG_DEBUG_WX 16908c8e685SHeiko Carstens void ptdump_check_wx(void) 17008c8e685SHeiko Carstens { 17108c8e685SHeiko Carstens struct pg_state st = { 17208c8e685SHeiko Carstens .ptdump = { 17308c8e685SHeiko Carstens .note_page = note_page, 17408c8e685SHeiko Carstens .range = (struct ptdump_range[]) { 17508c8e685SHeiko Carstens {.start = 0, .end = max_addr}, 17608c8e685SHeiko Carstens {.start = 0, .end = 0}, 17708c8e685SHeiko Carstens } 17808c8e685SHeiko Carstens }, 17908c8e685SHeiko Carstens .seq = NULL, 18008c8e685SHeiko Carstens .level = -1, 18108c8e685SHeiko Carstens .current_prot = 0, 18208c8e685SHeiko Carstens .check_wx = true, 18308c8e685SHeiko Carstens .wx_pages = 0, 18408c8e685SHeiko Carstens .start_address = 0, 18508c8e685SHeiko Carstens .marker = (struct addr_marker[]) { 18608c8e685SHeiko Carstens { .start_address = 0, .name = NULL}, 18708c8e685SHeiko Carstens { .start_address = -1, .name = NULL}, 18808c8e685SHeiko Carstens }, 18908c8e685SHeiko Carstens }; 19008c8e685SHeiko Carstens 19108c8e685SHeiko Carstens if (!MACHINE_HAS_NX) 19208c8e685SHeiko Carstens return; 19308c8e685SHeiko Carstens ptdump_walk_pgd(&st.ptdump, &init_mm, NULL); 19408c8e685SHeiko Carstens if (st.wx_pages) 19508c8e685SHeiko Carstens pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n", st.wx_pages); 19608c8e685SHeiko Carstens else 19708c8e685SHeiko Carstens pr_info("Checked W+X mappings: passed, no unexpected W+X pages found\n"); 19808c8e685SHeiko Carstens } 19908c8e685SHeiko Carstens #endif /* CONFIG_DEBUG_WX */ 20008c8e685SHeiko Carstens 20108c8e685SHeiko Carstens #ifdef CONFIG_PTDUMP_DEBUGFS 202e76e82d7SHeiko Carstens static int ptdump_show(struct seq_file *m, void *v) 203e76e82d7SHeiko Carstens { 2049d719d39SHeiko Carstens struct pg_state st = { 2059d719d39SHeiko Carstens .ptdump = { 2069d719d39SHeiko Carstens .note_page = note_page, 2079d719d39SHeiko Carstens .range = (struct ptdump_range[]) { 2089d719d39SHeiko Carstens {.start = 0, .end = max_addr}, 2099d719d39SHeiko Carstens {.start = 0, .end = 0}, 2109d719d39SHeiko Carstens } 2119d719d39SHeiko Carstens }, 2129d719d39SHeiko Carstens .seq = m, 2139d719d39SHeiko Carstens .level = -1, 2149d719d39SHeiko Carstens .current_prot = 0, 21508c8e685SHeiko Carstens .check_wx = false, 21608c8e685SHeiko Carstens .wx_pages = 0, 2179d719d39SHeiko Carstens .start_address = 0, 2189d719d39SHeiko Carstens .marker = address_markers, 2199d719d39SHeiko Carstens }; 2209d719d39SHeiko Carstens 22136c2733cSHeiko Carstens get_online_mems(); 222da1694adSHeiko Carstens mutex_lock(&cpa_mutex); 2239d719d39SHeiko Carstens ptdump_walk_pgd(&st.ptdump, &init_mm, NULL); 224da1694adSHeiko Carstens mutex_unlock(&cpa_mutex); 22536c2733cSHeiko Carstens put_online_mems(); 226e76e82d7SHeiko Carstens return 0; 227e76e82d7SHeiko Carstens } 2289d719d39SHeiko Carstens DEFINE_SHOW_ATTRIBUTE(ptdump); 22908c8e685SHeiko Carstens #endif /* CONFIG_PTDUMP_DEBUGFS */ 230e76e82d7SHeiko Carstens 231e76e82d7SHeiko Carstens static int pt_dump_init(void) 232e76e82d7SHeiko Carstens { 233e76e82d7SHeiko Carstens /* 234e76e82d7SHeiko Carstens * Figure out the maximum virtual address being accessible with the 235e76e82d7SHeiko Carstens * kernel ASCE. We need this to keep the page table walker functions 236e76e82d7SHeiko Carstens * from accessing non-existent entries. 237e76e82d7SHeiko Carstens */ 238e76e82d7SHeiko Carstens max_addr = (S390_lowcore.kernel_asce & _REGION_ENTRY_TYPE_MASK) >> 2; 239e76e82d7SHeiko Carstens max_addr = 1UL << (max_addr * 11 + 31); 240e670e64aSVasily Gorbik address_markers[IDENTITY_AFTER_END_NR].start_address = memory_end; 241c972cc60SHeiko Carstens address_markers[MODULES_NR].start_address = MODULES_VADDR; 242e670e64aSVasily Gorbik address_markers[MODULES_END_NR].start_address = MODULES_END; 243e76e82d7SHeiko Carstens address_markers[VMEMMAP_NR].start_address = (unsigned long) vmemmap; 244e670e64aSVasily Gorbik address_markers[VMEMMAP_END_NR].start_address = (unsigned long)vmemmap + vmemmap_size; 245e76e82d7SHeiko Carstens address_markers[VMALLOC_NR].start_address = VMALLOC_START; 246e670e64aSVasily Gorbik address_markers[VMALLOC_END_NR].start_address = VMALLOC_END; 247*48111b48SHeiko Carstens #ifdef CONFIG_PTDUMP_DEBUGFS 248e76e82d7SHeiko Carstens debugfs_create_file("kernel_page_tables", 0400, NULL, NULL, &ptdump_fops); 249*48111b48SHeiko Carstens #endif /* CONFIG_PTDUMP_DEBUGFS */ 250e76e82d7SHeiko Carstens return 0; 251e76e82d7SHeiko Carstens } 252e76e82d7SHeiko Carstens device_initcall(pt_dump_init); 253