xref: /linux/mm/ptdump.c (revision 100c85421b52e41269ada88f7d71a6b8a06c7a11)
130d621f6SSteven Price // SPDX-License-Identifier: GPL-2.0
230d621f6SSteven Price 
330d621f6SSteven Price #include <linux/pagewalk.h>
4*565474afSChristophe Leroy #include <linux/debugfs.h>
530d621f6SSteven Price #include <linux/ptdump.h>
630d621f6SSteven Price #include <linux/kasan.h>
730d621f6SSteven Price 
80fea6e9aSAndrey Konovalov #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
930d621f6SSteven Price /*
1030d621f6SSteven Price  * This is an optimization for KASAN=y case. Since all kasan page tables
1130d621f6SSteven Price  * eventually point to the kasan_early_shadow_page we could call note_page()
1230d621f6SSteven Price  * right away without walking through lower level page tables. This saves
1330d621f6SSteven Price  * us dozens of seconds (minutes for 5-level config) while checking for
1430d621f6SSteven Price  * W+X mapping or reading kernel_page_tables debugfs file.
1530d621f6SSteven Price  */
1630d621f6SSteven Price static inline int note_kasan_page_table(struct mm_walk *walk,
1730d621f6SSteven Price 					unsigned long addr)
1830d621f6SSteven Price {
1930d621f6SSteven Price 	struct ptdump_state *st = walk->private;
2030d621f6SSteven Price 
21f8f0d0b6SSteven Price 	st->note_page(st, addr, 4, pte_val(kasan_early_shadow_pte[0]));
2230d621f6SSteven Price 
2330d621f6SSteven Price 	walk->action = ACTION_CONTINUE;
2430d621f6SSteven Price 
2530d621f6SSteven Price 	return 0;
2630d621f6SSteven Price }
2730d621f6SSteven Price #endif
2830d621f6SSteven Price 
2930d621f6SSteven Price static int ptdump_pgd_entry(pgd_t *pgd, unsigned long addr,
3030d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
3130d621f6SSteven Price {
3230d621f6SSteven Price 	struct ptdump_state *st = walk->private;
3330d621f6SSteven Price 	pgd_t val = READ_ONCE(*pgd);
3430d621f6SSteven Price 
350fea6e9aSAndrey Konovalov #if CONFIG_PGTABLE_LEVELS > 4 && \
360fea6e9aSAndrey Konovalov 		(defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS))
3730d621f6SSteven Price 	if (pgd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_p4d)))
3830d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
3930d621f6SSteven Price #endif
4030d621f6SSteven Price 
411494e0c3SSteven Price 	if (st->effective_prot)
421494e0c3SSteven Price 		st->effective_prot(st, 0, pgd_val(val));
431494e0c3SSteven Price 
44d8d55f56SMuchun Song 	if (pgd_leaf(val)) {
45f8f0d0b6SSteven Price 		st->note_page(st, addr, 0, pgd_val(val));
46d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
47d8d55f56SMuchun Song 	}
4830d621f6SSteven Price 
4930d621f6SSteven Price 	return 0;
5030d621f6SSteven Price }
5130d621f6SSteven Price 
5230d621f6SSteven Price static int ptdump_p4d_entry(p4d_t *p4d, unsigned long addr,
5330d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
5430d621f6SSteven Price {
5530d621f6SSteven Price 	struct ptdump_state *st = walk->private;
5630d621f6SSteven Price 	p4d_t val = READ_ONCE(*p4d);
5730d621f6SSteven Price 
580fea6e9aSAndrey Konovalov #if CONFIG_PGTABLE_LEVELS > 3 && \
590fea6e9aSAndrey Konovalov 		(defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS))
6030d621f6SSteven Price 	if (p4d_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pud)))
6130d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
6230d621f6SSteven Price #endif
6330d621f6SSteven Price 
641494e0c3SSteven Price 	if (st->effective_prot)
651494e0c3SSteven Price 		st->effective_prot(st, 1, p4d_val(val));
661494e0c3SSteven Price 
67d8d55f56SMuchun Song 	if (p4d_leaf(val)) {
68f8f0d0b6SSteven Price 		st->note_page(st, addr, 1, p4d_val(val));
69d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
70d8d55f56SMuchun Song 	}
7130d621f6SSteven Price 
7230d621f6SSteven Price 	return 0;
7330d621f6SSteven Price }
7430d621f6SSteven Price 
7530d621f6SSteven Price static int ptdump_pud_entry(pud_t *pud, unsigned long addr,
7630d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
7730d621f6SSteven Price {
7830d621f6SSteven Price 	struct ptdump_state *st = walk->private;
7930d621f6SSteven Price 	pud_t val = READ_ONCE(*pud);
8030d621f6SSteven Price 
810fea6e9aSAndrey Konovalov #if CONFIG_PGTABLE_LEVELS > 2 && \
820fea6e9aSAndrey Konovalov 		(defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS))
8330d621f6SSteven Price 	if (pud_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pmd)))
8430d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
8530d621f6SSteven Price #endif
8630d621f6SSteven Price 
871494e0c3SSteven Price 	if (st->effective_prot)
881494e0c3SSteven Price 		st->effective_prot(st, 2, pud_val(val));
891494e0c3SSteven Price 
90d8d55f56SMuchun Song 	if (pud_leaf(val)) {
91f8f0d0b6SSteven Price 		st->note_page(st, addr, 2, pud_val(val));
92d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
93d8d55f56SMuchun Song 	}
9430d621f6SSteven Price 
9530d621f6SSteven Price 	return 0;
9630d621f6SSteven Price }
9730d621f6SSteven Price 
9830d621f6SSteven Price static int ptdump_pmd_entry(pmd_t *pmd, unsigned long addr,
9930d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
10030d621f6SSteven Price {
10130d621f6SSteven Price 	struct ptdump_state *st = walk->private;
10230d621f6SSteven Price 	pmd_t val = READ_ONCE(*pmd);
10330d621f6SSteven Price 
1040fea6e9aSAndrey Konovalov #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
10530d621f6SSteven Price 	if (pmd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pte)))
10630d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
10730d621f6SSteven Price #endif
10830d621f6SSteven Price 
1091494e0c3SSteven Price 	if (st->effective_prot)
1101494e0c3SSteven Price 		st->effective_prot(st, 3, pmd_val(val));
111d8d55f56SMuchun Song 	if (pmd_leaf(val)) {
112f8f0d0b6SSteven Price 		st->note_page(st, addr, 3, pmd_val(val));
113d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
114d8d55f56SMuchun Song 	}
11530d621f6SSteven Price 
11630d621f6SSteven Price 	return 0;
11730d621f6SSteven Price }
11830d621f6SSteven Price 
11930d621f6SSteven Price static int ptdump_pte_entry(pte_t *pte, unsigned long addr,
12030d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
12130d621f6SSteven Price {
12230d621f6SSteven Price 	struct ptdump_state *st = walk->private;
123426931e7SRyan Roberts 	pte_t val = ptep_get_lockless(pte);
12430d621f6SSteven Price 
1251494e0c3SSteven Price 	if (st->effective_prot)
1261494e0c3SSteven Price 		st->effective_prot(st, 4, pte_val(val));
1271494e0c3SSteven Price 
1281494e0c3SSteven Price 	st->note_page(st, addr, 4, pte_val(val));
12930d621f6SSteven Price 
13030d621f6SSteven Price 	return 0;
13130d621f6SSteven Price }
13230d621f6SSteven Price 
13330d621f6SSteven Price static int ptdump_hole(unsigned long addr, unsigned long next,
13430d621f6SSteven Price 		       int depth, struct mm_walk *walk)
13530d621f6SSteven Price {
13630d621f6SSteven Price 	struct ptdump_state *st = walk->private;
13730d621f6SSteven Price 
138f8f0d0b6SSteven Price 	st->note_page(st, addr, depth, 0);
13930d621f6SSteven Price 
14030d621f6SSteven Price 	return 0;
14130d621f6SSteven Price }
14230d621f6SSteven Price 
14330d621f6SSteven Price static const struct mm_walk_ops ptdump_ops = {
14430d621f6SSteven Price 	.pgd_entry	= ptdump_pgd_entry,
14530d621f6SSteven Price 	.p4d_entry	= ptdump_p4d_entry,
14630d621f6SSteven Price 	.pud_entry	= ptdump_pud_entry,
14730d621f6SSteven Price 	.pmd_entry	= ptdump_pmd_entry,
14830d621f6SSteven Price 	.pte_entry	= ptdump_pte_entry,
14930d621f6SSteven Price 	.pte_hole	= ptdump_hole,
15030d621f6SSteven Price };
15130d621f6SSteven Price 
152e47690d7SSteven Price void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm, pgd_t *pgd)
15330d621f6SSteven Price {
15430d621f6SSteven Price 	const struct ptdump_range *range = st->range;
15530d621f6SSteven Price 
1568782fb61SSteven Price 	mmap_write_lock(mm);
15730d621f6SSteven Price 	while (range->start != range->end) {
15830d621f6SSteven Price 		walk_page_range_novma(mm, range->start, range->end,
159e47690d7SSteven Price 				      &ptdump_ops, pgd, st);
16030d621f6SSteven Price 		range++;
16130d621f6SSteven Price 	}
1628782fb61SSteven Price 	mmap_write_unlock(mm);
16330d621f6SSteven Price 
16430d621f6SSteven Price 	/* Flush out the last page */
165f8f0d0b6SSteven Price 	st->note_page(st, 0, -1, 0);
16630d621f6SSteven Price }
167*565474afSChristophe Leroy 
168*565474afSChristophe Leroy static int check_wx_show(struct seq_file *m, void *v)
169*565474afSChristophe Leroy {
170*565474afSChristophe Leroy 	if (ptdump_check_wx())
171*565474afSChristophe Leroy 		seq_puts(m, "SUCCESS\n");
172*565474afSChristophe Leroy 	else
173*565474afSChristophe Leroy 		seq_puts(m, "FAILED\n");
174*565474afSChristophe Leroy 
175*565474afSChristophe Leroy 	return 0;
176*565474afSChristophe Leroy }
177*565474afSChristophe Leroy 
178*565474afSChristophe Leroy DEFINE_SHOW_ATTRIBUTE(check_wx);
179*565474afSChristophe Leroy 
180*565474afSChristophe Leroy static int ptdump_debugfs_init(void)
181*565474afSChristophe Leroy {
182*565474afSChristophe Leroy 	debugfs_create_file("check_wx_pages", 0400, NULL, NULL, &check_wx_fops);
183*565474afSChristophe Leroy 
184*565474afSChristophe Leroy 	return 0;
185*565474afSChristophe Leroy }
186*565474afSChristophe Leroy 
187*565474afSChristophe Leroy device_initcall(ptdump_debugfs_init);
188