162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/set_memory.h>
362306a36Sopenharmony_ci#include <linux/ptdump.h>
462306a36Sopenharmony_ci#include <linux/seq_file.h>
562306a36Sopenharmony_ci#include <linux/debugfs.h>
662306a36Sopenharmony_ci#include <linux/mm.h>
762306a36Sopenharmony_ci#include <linux/kfence.h>
862306a36Sopenharmony_ci#include <linux/kasan.h>
962306a36Sopenharmony_ci#include <asm/ptdump.h>
1062306a36Sopenharmony_ci#include <asm/kasan.h>
1162306a36Sopenharmony_ci#include <asm/abs_lowcore.h>
1262306a36Sopenharmony_ci#include <asm/nospec-branch.h>
1362306a36Sopenharmony_ci#include <asm/sections.h>
1462306a36Sopenharmony_ci#include <asm/maccess.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic unsigned long max_addr;
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistruct addr_marker {
1962306a36Sopenharmony_ci	unsigned long start_address;
2062306a36Sopenharmony_ci	const char *name;
2162306a36Sopenharmony_ci};
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cienum address_markers_idx {
2462306a36Sopenharmony_ci	IDENTITY_BEFORE_NR = 0,
2562306a36Sopenharmony_ci	IDENTITY_BEFORE_END_NR,
2662306a36Sopenharmony_ci	AMODE31_START_NR,
2762306a36Sopenharmony_ci	AMODE31_END_NR,
2862306a36Sopenharmony_ci	KERNEL_START_NR,
2962306a36Sopenharmony_ci	KERNEL_END_NR,
3062306a36Sopenharmony_ci#ifdef CONFIG_KFENCE
3162306a36Sopenharmony_ci	KFENCE_START_NR,
3262306a36Sopenharmony_ci	KFENCE_END_NR,
3362306a36Sopenharmony_ci#endif
3462306a36Sopenharmony_ci	IDENTITY_AFTER_NR,
3562306a36Sopenharmony_ci	IDENTITY_AFTER_END_NR,
3662306a36Sopenharmony_ci	VMEMMAP_NR,
3762306a36Sopenharmony_ci	VMEMMAP_END_NR,
3862306a36Sopenharmony_ci	VMALLOC_NR,
3962306a36Sopenharmony_ci	VMALLOC_END_NR,
4062306a36Sopenharmony_ci	MODULES_NR,
4162306a36Sopenharmony_ci	MODULES_END_NR,
4262306a36Sopenharmony_ci	ABS_LOWCORE_NR,
4362306a36Sopenharmony_ci	ABS_LOWCORE_END_NR,
4462306a36Sopenharmony_ci	MEMCPY_REAL_NR,
4562306a36Sopenharmony_ci	MEMCPY_REAL_END_NR,
4662306a36Sopenharmony_ci#ifdef CONFIG_KASAN
4762306a36Sopenharmony_ci	KASAN_SHADOW_START_NR,
4862306a36Sopenharmony_ci	KASAN_SHADOW_END_NR,
4962306a36Sopenharmony_ci#endif
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic struct addr_marker address_markers[] = {
5362306a36Sopenharmony_ci	[IDENTITY_BEFORE_NR]	= {0, "Identity Mapping Start"},
5462306a36Sopenharmony_ci	[IDENTITY_BEFORE_END_NR] = {(unsigned long)_stext, "Identity Mapping End"},
5562306a36Sopenharmony_ci	[AMODE31_START_NR]	= {0, "Amode31 Area Start"},
5662306a36Sopenharmony_ci	[AMODE31_END_NR]	= {0, "Amode31 Area End"},
5762306a36Sopenharmony_ci	[KERNEL_START_NR]	= {(unsigned long)_stext, "Kernel Image Start"},
5862306a36Sopenharmony_ci	[KERNEL_END_NR]		= {(unsigned long)_end, "Kernel Image End"},
5962306a36Sopenharmony_ci#ifdef CONFIG_KFENCE
6062306a36Sopenharmony_ci	[KFENCE_START_NR]	= {0, "KFence Pool Start"},
6162306a36Sopenharmony_ci	[KFENCE_END_NR]		= {0, "KFence Pool End"},
6262306a36Sopenharmony_ci#endif
6362306a36Sopenharmony_ci	[IDENTITY_AFTER_NR]	= {(unsigned long)_end, "Identity Mapping Start"},
6462306a36Sopenharmony_ci	[IDENTITY_AFTER_END_NR]	= {0, "Identity Mapping End"},
6562306a36Sopenharmony_ci	[VMEMMAP_NR]		= {0, "vmemmap Area Start"},
6662306a36Sopenharmony_ci	[VMEMMAP_END_NR]	= {0, "vmemmap Area End"},
6762306a36Sopenharmony_ci	[VMALLOC_NR]		= {0, "vmalloc Area Start"},
6862306a36Sopenharmony_ci	[VMALLOC_END_NR]	= {0, "vmalloc Area End"},
6962306a36Sopenharmony_ci	[MODULES_NR]		= {0, "Modules Area Start"},
7062306a36Sopenharmony_ci	[MODULES_END_NR]	= {0, "Modules Area End"},
7162306a36Sopenharmony_ci	[ABS_LOWCORE_NR]	= {0, "Lowcore Area Start"},
7262306a36Sopenharmony_ci	[ABS_LOWCORE_END_NR]	= {0, "Lowcore Area End"},
7362306a36Sopenharmony_ci	[MEMCPY_REAL_NR]	= {0, "Real Memory Copy Area Start"},
7462306a36Sopenharmony_ci	[MEMCPY_REAL_END_NR]	= {0, "Real Memory Copy Area End"},
7562306a36Sopenharmony_ci#ifdef CONFIG_KASAN
7662306a36Sopenharmony_ci	[KASAN_SHADOW_START_NR]	= {KASAN_SHADOW_START, "Kasan Shadow Start"},
7762306a36Sopenharmony_ci	[KASAN_SHADOW_END_NR]	= {KASAN_SHADOW_END, "Kasan Shadow End"},
7862306a36Sopenharmony_ci#endif
7962306a36Sopenharmony_ci	{ -1, NULL }
8062306a36Sopenharmony_ci};
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistruct pg_state {
8362306a36Sopenharmony_ci	struct ptdump_state ptdump;
8462306a36Sopenharmony_ci	struct seq_file *seq;
8562306a36Sopenharmony_ci	int level;
8662306a36Sopenharmony_ci	unsigned int current_prot;
8762306a36Sopenharmony_ci	bool check_wx;
8862306a36Sopenharmony_ci	unsigned long wx_pages;
8962306a36Sopenharmony_ci	unsigned long start_address;
9062306a36Sopenharmony_ci	const struct addr_marker *marker;
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci#define pt_dump_seq_printf(m, fmt, args...)	\
9462306a36Sopenharmony_ci({						\
9562306a36Sopenharmony_ci	struct seq_file *__m = (m);		\
9662306a36Sopenharmony_ci						\
9762306a36Sopenharmony_ci	if (__m)				\
9862306a36Sopenharmony_ci		seq_printf(__m, fmt, ##args);	\
9962306a36Sopenharmony_ci})
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci#define pt_dump_seq_puts(m, fmt)		\
10262306a36Sopenharmony_ci({						\
10362306a36Sopenharmony_ci	struct seq_file *__m = (m);		\
10462306a36Sopenharmony_ci						\
10562306a36Sopenharmony_ci	if (__m)				\
10662306a36Sopenharmony_ci		seq_printf(__m, fmt);		\
10762306a36Sopenharmony_ci})
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void print_prot(struct seq_file *m, unsigned int pr, int level)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	static const char * const level_name[] =
11262306a36Sopenharmony_ci		{ "ASCE", "PGD", "PUD", "PMD", "PTE" };
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	pt_dump_seq_printf(m, "%s ", level_name[level]);
11562306a36Sopenharmony_ci	if (pr & _PAGE_INVALID) {
11662306a36Sopenharmony_ci		pt_dump_seq_printf(m, "I\n");
11762306a36Sopenharmony_ci		return;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci	pt_dump_seq_puts(m, (pr & _PAGE_PROTECT) ? "RO " : "RW ");
12062306a36Sopenharmony_ci	pt_dump_seq_puts(m, (pr & _PAGE_NOEXEC) ? "NX\n" : "X\n");
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic void note_prot_wx(struct pg_state *st, unsigned long addr)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci#ifdef CONFIG_DEBUG_WX
12662306a36Sopenharmony_ci	if (!st->check_wx)
12762306a36Sopenharmony_ci		return;
12862306a36Sopenharmony_ci	if (st->current_prot & _PAGE_INVALID)
12962306a36Sopenharmony_ci		return;
13062306a36Sopenharmony_ci	if (st->current_prot & _PAGE_PROTECT)
13162306a36Sopenharmony_ci		return;
13262306a36Sopenharmony_ci	if (st->current_prot & _PAGE_NOEXEC)
13362306a36Sopenharmony_ci		return;
13462306a36Sopenharmony_ci	/*
13562306a36Sopenharmony_ci	 * The first lowcore page is W+X if spectre mitigations are using
13662306a36Sopenharmony_ci	 * trampolines or the BEAR enhancements facility is not installed,
13762306a36Sopenharmony_ci	 * in which case we have two lpswe instructions in lowcore that need
13862306a36Sopenharmony_ci	 * to be executable.
13962306a36Sopenharmony_ci	 */
14062306a36Sopenharmony_ci	if (addr == PAGE_SIZE && (nospec_uses_trampoline() || !static_key_enabled(&cpu_has_bear)))
14162306a36Sopenharmony_ci		return;
14262306a36Sopenharmony_ci	WARN_ONCE(1, "s390/mm: Found insecure W+X mapping at address %pS\n",
14362306a36Sopenharmony_ci		  (void *)st->start_address);
14462306a36Sopenharmony_ci	st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
14562306a36Sopenharmony_ci#endif /* CONFIG_DEBUG_WX */
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic void note_page(struct ptdump_state *pt_st, unsigned long addr, int level, u64 val)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	int width = sizeof(unsigned long) * 2;
15162306a36Sopenharmony_ci	static const char units[] = "KMGTPE";
15262306a36Sopenharmony_ci	const char *unit = units;
15362306a36Sopenharmony_ci	unsigned long delta;
15462306a36Sopenharmony_ci	struct pg_state *st;
15562306a36Sopenharmony_ci	struct seq_file *m;
15662306a36Sopenharmony_ci	unsigned int prot;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	st = container_of(pt_st, struct pg_state, ptdump);
15962306a36Sopenharmony_ci	m = st->seq;
16062306a36Sopenharmony_ci	prot = val & (_PAGE_PROTECT | _PAGE_NOEXEC);
16162306a36Sopenharmony_ci	if (level == 4 && (val & _PAGE_INVALID))
16262306a36Sopenharmony_ci		prot = _PAGE_INVALID;
16362306a36Sopenharmony_ci	/* For pmd_none() & friends val gets passed as zero. */
16462306a36Sopenharmony_ci	if (level != 4 && !val)
16562306a36Sopenharmony_ci		prot = _PAGE_INVALID;
16662306a36Sopenharmony_ci	/* Final flush from generic code. */
16762306a36Sopenharmony_ci	if (level == -1)
16862306a36Sopenharmony_ci		addr = max_addr;
16962306a36Sopenharmony_ci	if (st->level == -1) {
17062306a36Sopenharmony_ci		pt_dump_seq_printf(m, "---[ %s ]---\n", st->marker->name);
17162306a36Sopenharmony_ci		st->start_address = addr;
17262306a36Sopenharmony_ci		st->current_prot = prot;
17362306a36Sopenharmony_ci		st->level = level;
17462306a36Sopenharmony_ci	} else if (prot != st->current_prot || level != st->level ||
17562306a36Sopenharmony_ci		   addr >= st->marker[1].start_address) {
17662306a36Sopenharmony_ci		note_prot_wx(st, addr);
17762306a36Sopenharmony_ci		pt_dump_seq_printf(m, "0x%0*lx-0x%0*lx ",
17862306a36Sopenharmony_ci				   width, st->start_address,
17962306a36Sopenharmony_ci				   width, addr);
18062306a36Sopenharmony_ci		delta = (addr - st->start_address) >> 10;
18162306a36Sopenharmony_ci		while (!(delta & 0x3ff) && unit[1]) {
18262306a36Sopenharmony_ci			delta >>= 10;
18362306a36Sopenharmony_ci			unit++;
18462306a36Sopenharmony_ci		}
18562306a36Sopenharmony_ci		pt_dump_seq_printf(m, "%9lu%c ", delta, *unit);
18662306a36Sopenharmony_ci		print_prot(m, st->current_prot, st->level);
18762306a36Sopenharmony_ci		while (addr >= st->marker[1].start_address) {
18862306a36Sopenharmony_ci			st->marker++;
18962306a36Sopenharmony_ci			pt_dump_seq_printf(m, "---[ %s ]---\n", st->marker->name);
19062306a36Sopenharmony_ci		}
19162306a36Sopenharmony_ci		st->start_address = addr;
19262306a36Sopenharmony_ci		st->current_prot = prot;
19362306a36Sopenharmony_ci		st->level = level;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci#ifdef CONFIG_DEBUG_WX
19862306a36Sopenharmony_civoid ptdump_check_wx(void)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	struct pg_state st = {
20162306a36Sopenharmony_ci		.ptdump = {
20262306a36Sopenharmony_ci			.note_page = note_page,
20362306a36Sopenharmony_ci			.range = (struct ptdump_range[]) {
20462306a36Sopenharmony_ci				{.start = 0, .end = max_addr},
20562306a36Sopenharmony_ci				{.start = 0, .end = 0},
20662306a36Sopenharmony_ci			}
20762306a36Sopenharmony_ci		},
20862306a36Sopenharmony_ci		.seq = NULL,
20962306a36Sopenharmony_ci		.level = -1,
21062306a36Sopenharmony_ci		.current_prot = 0,
21162306a36Sopenharmony_ci		.check_wx = true,
21262306a36Sopenharmony_ci		.wx_pages = 0,
21362306a36Sopenharmony_ci		.start_address = 0,
21462306a36Sopenharmony_ci		.marker = (struct addr_marker[]) {
21562306a36Sopenharmony_ci			{ .start_address =  0, .name = NULL},
21662306a36Sopenharmony_ci			{ .start_address = -1, .name = NULL},
21762306a36Sopenharmony_ci		},
21862306a36Sopenharmony_ci	};
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	if (!MACHINE_HAS_NX)
22162306a36Sopenharmony_ci		return;
22262306a36Sopenharmony_ci	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
22362306a36Sopenharmony_ci	if (st.wx_pages)
22462306a36Sopenharmony_ci		pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n", st.wx_pages);
22562306a36Sopenharmony_ci	else
22662306a36Sopenharmony_ci		pr_info("Checked W+X mappings: passed, no %sW+X pages found\n",
22762306a36Sopenharmony_ci			(nospec_uses_trampoline() || !static_key_enabled(&cpu_has_bear)) ?
22862306a36Sopenharmony_ci			"unexpected " : "");
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci#endif /* CONFIG_DEBUG_WX */
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci#ifdef CONFIG_PTDUMP_DEBUGFS
23362306a36Sopenharmony_cistatic int ptdump_show(struct seq_file *m, void *v)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	struct pg_state st = {
23662306a36Sopenharmony_ci		.ptdump = {
23762306a36Sopenharmony_ci			.note_page = note_page,
23862306a36Sopenharmony_ci			.range = (struct ptdump_range[]) {
23962306a36Sopenharmony_ci				{.start = 0, .end = max_addr},
24062306a36Sopenharmony_ci				{.start = 0, .end = 0},
24162306a36Sopenharmony_ci			}
24262306a36Sopenharmony_ci		},
24362306a36Sopenharmony_ci		.seq = m,
24462306a36Sopenharmony_ci		.level = -1,
24562306a36Sopenharmony_ci		.current_prot = 0,
24662306a36Sopenharmony_ci		.check_wx = false,
24762306a36Sopenharmony_ci		.wx_pages = 0,
24862306a36Sopenharmony_ci		.start_address = 0,
24962306a36Sopenharmony_ci		.marker = address_markers,
25062306a36Sopenharmony_ci	};
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	get_online_mems();
25362306a36Sopenharmony_ci	mutex_lock(&cpa_mutex);
25462306a36Sopenharmony_ci	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
25562306a36Sopenharmony_ci	mutex_unlock(&cpa_mutex);
25662306a36Sopenharmony_ci	put_online_mems();
25762306a36Sopenharmony_ci	return 0;
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(ptdump);
26062306a36Sopenharmony_ci#endif /* CONFIG_PTDUMP_DEBUGFS */
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci/*
26362306a36Sopenharmony_ci * Heapsort from lib/sort.c is not a stable sorting algorithm, do a simple
26462306a36Sopenharmony_ci * insertion sort to preserve the original order of markers with the same
26562306a36Sopenharmony_ci * start address.
26662306a36Sopenharmony_ci */
26762306a36Sopenharmony_cistatic void sort_address_markers(void)
26862306a36Sopenharmony_ci{
26962306a36Sopenharmony_ci	struct addr_marker tmp;
27062306a36Sopenharmony_ci	int i, j;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	for (i = 1; i < ARRAY_SIZE(address_markers) - 1; i++) {
27362306a36Sopenharmony_ci		tmp = address_markers[i];
27462306a36Sopenharmony_ci		for (j = i - 1; j >= 0 && address_markers[j].start_address > tmp.start_address; j--)
27562306a36Sopenharmony_ci			address_markers[j + 1] = address_markers[j];
27662306a36Sopenharmony_ci		address_markers[j + 1] = tmp;
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int pt_dump_init(void)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci#ifdef CONFIG_KFENCE
28362306a36Sopenharmony_ci	unsigned long kfence_start = (unsigned long)__kfence_pool;
28462306a36Sopenharmony_ci#endif
28562306a36Sopenharmony_ci	/*
28662306a36Sopenharmony_ci	 * Figure out the maximum virtual address being accessible with the
28762306a36Sopenharmony_ci	 * kernel ASCE. We need this to keep the page table walker functions
28862306a36Sopenharmony_ci	 * from accessing non-existent entries.
28962306a36Sopenharmony_ci	 */
29062306a36Sopenharmony_ci	max_addr = (S390_lowcore.kernel_asce & _REGION_ENTRY_TYPE_MASK) >> 2;
29162306a36Sopenharmony_ci	max_addr = 1UL << (max_addr * 11 + 31);
29262306a36Sopenharmony_ci	address_markers[IDENTITY_AFTER_END_NR].start_address = ident_map_size;
29362306a36Sopenharmony_ci	address_markers[AMODE31_START_NR].start_address = (unsigned long)__samode31;
29462306a36Sopenharmony_ci	address_markers[AMODE31_END_NR].start_address = (unsigned long)__eamode31;
29562306a36Sopenharmony_ci	address_markers[MODULES_NR].start_address = MODULES_VADDR;
29662306a36Sopenharmony_ci	address_markers[MODULES_END_NR].start_address = MODULES_END;
29762306a36Sopenharmony_ci	address_markers[ABS_LOWCORE_NR].start_address = __abs_lowcore;
29862306a36Sopenharmony_ci	address_markers[ABS_LOWCORE_END_NR].start_address = __abs_lowcore + ABS_LOWCORE_MAP_SIZE;
29962306a36Sopenharmony_ci	address_markers[MEMCPY_REAL_NR].start_address = __memcpy_real_area;
30062306a36Sopenharmony_ci	address_markers[MEMCPY_REAL_END_NR].start_address = __memcpy_real_area + MEMCPY_REAL_SIZE;
30162306a36Sopenharmony_ci	address_markers[VMEMMAP_NR].start_address = (unsigned long) vmemmap;
30262306a36Sopenharmony_ci	address_markers[VMEMMAP_END_NR].start_address = (unsigned long)vmemmap + vmemmap_size;
30362306a36Sopenharmony_ci	address_markers[VMALLOC_NR].start_address = VMALLOC_START;
30462306a36Sopenharmony_ci	address_markers[VMALLOC_END_NR].start_address = VMALLOC_END;
30562306a36Sopenharmony_ci#ifdef CONFIG_KFENCE
30662306a36Sopenharmony_ci	address_markers[KFENCE_START_NR].start_address = kfence_start;
30762306a36Sopenharmony_ci	address_markers[KFENCE_END_NR].start_address = kfence_start + KFENCE_POOL_SIZE;
30862306a36Sopenharmony_ci#endif
30962306a36Sopenharmony_ci	sort_address_markers();
31062306a36Sopenharmony_ci#ifdef CONFIG_PTDUMP_DEBUGFS
31162306a36Sopenharmony_ci	debugfs_create_file("kernel_page_tables", 0400, NULL, NULL, &ptdump_fops);
31262306a36Sopenharmony_ci#endif /* CONFIG_PTDUMP_DEBUGFS */
31362306a36Sopenharmony_ci	return 0;
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_cidevice_initcall(pt_dump_init);
316