xref: /linux/arch/s390/mm/dump_pagetables.c (revision 48111b4838480d1357783f4231c351bb2ba2d27d)
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