xref: /openbmc/linux/mm/ptdump.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
130d621f6SSteven Price // SPDX-License-Identifier: GPL-2.0
230d621f6SSteven Price 
330d621f6SSteven Price #include <linux/pagewalk.h>
430d621f6SSteven Price #include <linux/ptdump.h>
530d621f6SSteven Price #include <linux/kasan.h>
630d621f6SSteven Price 
70fea6e9aSAndrey Konovalov #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
830d621f6SSteven Price /*
930d621f6SSteven Price  * This is an optimization for KASAN=y case. Since all kasan page tables
1030d621f6SSteven Price  * eventually point to the kasan_early_shadow_page we could call note_page()
1130d621f6SSteven Price  * right away without walking through lower level page tables. This saves
1230d621f6SSteven Price  * us dozens of seconds (minutes for 5-level config) while checking for
1330d621f6SSteven Price  * W+X mapping or reading kernel_page_tables debugfs file.
1430d621f6SSteven Price  */
1530d621f6SSteven Price static inline int note_kasan_page_table(struct mm_walk *walk,
1630d621f6SSteven Price 					unsigned long addr)
1730d621f6SSteven Price {
1830d621f6SSteven Price 	struct ptdump_state *st = walk->private;
1930d621f6SSteven Price 
20f8f0d0b6SSteven Price 	st->note_page(st, addr, 4, pte_val(kasan_early_shadow_pte[0]));
2130d621f6SSteven Price 
2230d621f6SSteven Price 	walk->action = ACTION_CONTINUE;
2330d621f6SSteven Price 
2430d621f6SSteven Price 	return 0;
2530d621f6SSteven Price }
2630d621f6SSteven Price #endif
2730d621f6SSteven Price 
2830d621f6SSteven Price static int ptdump_pgd_entry(pgd_t *pgd, unsigned long addr,
2930d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
3030d621f6SSteven Price {
3130d621f6SSteven Price 	struct ptdump_state *st = walk->private;
3230d621f6SSteven Price 	pgd_t val = READ_ONCE(*pgd);
3330d621f6SSteven Price 
340fea6e9aSAndrey Konovalov #if CONFIG_PGTABLE_LEVELS > 4 && \
350fea6e9aSAndrey Konovalov 		(defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS))
3630d621f6SSteven Price 	if (pgd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_p4d)))
3730d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
3830d621f6SSteven Price #endif
3930d621f6SSteven Price 
401494e0c3SSteven Price 	if (st->effective_prot)
411494e0c3SSteven Price 		st->effective_prot(st, 0, pgd_val(val));
421494e0c3SSteven Price 
43d8d55f56SMuchun Song 	if (pgd_leaf(val)) {
44f8f0d0b6SSteven Price 		st->note_page(st, addr, 0, pgd_val(val));
45d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
46d8d55f56SMuchun Song 	}
4730d621f6SSteven Price 
4830d621f6SSteven Price 	return 0;
4930d621f6SSteven Price }
5030d621f6SSteven Price 
5130d621f6SSteven Price static int ptdump_p4d_entry(p4d_t *p4d, unsigned long addr,
5230d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
5330d621f6SSteven Price {
5430d621f6SSteven Price 	struct ptdump_state *st = walk->private;
5530d621f6SSteven Price 	p4d_t val = READ_ONCE(*p4d);
5630d621f6SSteven Price 
570fea6e9aSAndrey Konovalov #if CONFIG_PGTABLE_LEVELS > 3 && \
580fea6e9aSAndrey Konovalov 		(defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS))
5930d621f6SSteven Price 	if (p4d_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pud)))
6030d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
6130d621f6SSteven Price #endif
6230d621f6SSteven Price 
631494e0c3SSteven Price 	if (st->effective_prot)
641494e0c3SSteven Price 		st->effective_prot(st, 1, p4d_val(val));
651494e0c3SSteven Price 
66d8d55f56SMuchun Song 	if (p4d_leaf(val)) {
67f8f0d0b6SSteven Price 		st->note_page(st, addr, 1, p4d_val(val));
68d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
69d8d55f56SMuchun Song 	}
7030d621f6SSteven Price 
7130d621f6SSteven Price 	return 0;
7230d621f6SSteven Price }
7330d621f6SSteven Price 
7430d621f6SSteven Price static int ptdump_pud_entry(pud_t *pud, unsigned long addr,
7530d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
7630d621f6SSteven Price {
7730d621f6SSteven Price 	struct ptdump_state *st = walk->private;
7830d621f6SSteven Price 	pud_t val = READ_ONCE(*pud);
7930d621f6SSteven Price 
800fea6e9aSAndrey Konovalov #if CONFIG_PGTABLE_LEVELS > 2 && \
810fea6e9aSAndrey Konovalov 		(defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS))
8230d621f6SSteven Price 	if (pud_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pmd)))
8330d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
8430d621f6SSteven Price #endif
8530d621f6SSteven Price 
861494e0c3SSteven Price 	if (st->effective_prot)
871494e0c3SSteven Price 		st->effective_prot(st, 2, pud_val(val));
881494e0c3SSteven Price 
89d8d55f56SMuchun Song 	if (pud_leaf(val)) {
90f8f0d0b6SSteven Price 		st->note_page(st, addr, 2, pud_val(val));
91d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
92d8d55f56SMuchun Song 	}
9330d621f6SSteven Price 
9430d621f6SSteven Price 	return 0;
9530d621f6SSteven Price }
9630d621f6SSteven Price 
9730d621f6SSteven Price static int ptdump_pmd_entry(pmd_t *pmd, unsigned long addr,
9830d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
9930d621f6SSteven Price {
10030d621f6SSteven Price 	struct ptdump_state *st = walk->private;
10130d621f6SSteven Price 	pmd_t val = READ_ONCE(*pmd);
10230d621f6SSteven Price 
1030fea6e9aSAndrey Konovalov #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
10430d621f6SSteven Price 	if (pmd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pte)))
10530d621f6SSteven Price 		return note_kasan_page_table(walk, addr);
10630d621f6SSteven Price #endif
10730d621f6SSteven Price 
1081494e0c3SSteven Price 	if (st->effective_prot)
1091494e0c3SSteven Price 		st->effective_prot(st, 3, pmd_val(val));
110d8d55f56SMuchun Song 	if (pmd_leaf(val)) {
111f8f0d0b6SSteven Price 		st->note_page(st, addr, 3, pmd_val(val));
112d8d55f56SMuchun Song 		walk->action = ACTION_CONTINUE;
113d8d55f56SMuchun Song 	}
11430d621f6SSteven Price 
11530d621f6SSteven Price 	return 0;
11630d621f6SSteven Price }
11730d621f6SSteven Price 
11830d621f6SSteven Price static int ptdump_pte_entry(pte_t *pte, unsigned long addr,
11930d621f6SSteven Price 			    unsigned long next, struct mm_walk *walk)
12030d621f6SSteven Price {
12130d621f6SSteven Price 	struct ptdump_state *st = walk->private;
122*426931e7SRyan Roberts 	pte_t val = ptep_get_lockless(pte);
12330d621f6SSteven Price 
1241494e0c3SSteven Price 	if (st->effective_prot)
1251494e0c3SSteven Price 		st->effective_prot(st, 4, pte_val(val));
1261494e0c3SSteven Price 
1271494e0c3SSteven Price 	st->note_page(st, addr, 4, pte_val(val));
12830d621f6SSteven Price 
12930d621f6SSteven Price 	return 0;
13030d621f6SSteven Price }
13130d621f6SSteven Price 
13230d621f6SSteven Price static int ptdump_hole(unsigned long addr, unsigned long next,
13330d621f6SSteven Price 		       int depth, struct mm_walk *walk)
13430d621f6SSteven Price {
13530d621f6SSteven Price 	struct ptdump_state *st = walk->private;
13630d621f6SSteven Price 
137f8f0d0b6SSteven Price 	st->note_page(st, addr, depth, 0);
13830d621f6SSteven Price 
13930d621f6SSteven Price 	return 0;
14030d621f6SSteven Price }
14130d621f6SSteven Price 
14230d621f6SSteven Price static const struct mm_walk_ops ptdump_ops = {
14330d621f6SSteven Price 	.pgd_entry	= ptdump_pgd_entry,
14430d621f6SSteven Price 	.p4d_entry	= ptdump_p4d_entry,
14530d621f6SSteven Price 	.pud_entry	= ptdump_pud_entry,
14630d621f6SSteven Price 	.pmd_entry	= ptdump_pmd_entry,
14730d621f6SSteven Price 	.pte_entry	= ptdump_pte_entry,
14830d621f6SSteven Price 	.pte_hole	= ptdump_hole,
14930d621f6SSteven Price };
15030d621f6SSteven Price 
151e47690d7SSteven Price void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm, pgd_t *pgd)
15230d621f6SSteven Price {
15330d621f6SSteven Price 	const struct ptdump_range *range = st->range;
15430d621f6SSteven Price 
1558782fb61SSteven Price 	mmap_write_lock(mm);
15630d621f6SSteven Price 	while (range->start != range->end) {
15730d621f6SSteven Price 		walk_page_range_novma(mm, range->start, range->end,
158e47690d7SSteven Price 				      &ptdump_ops, pgd, st);
15930d621f6SSteven Price 		range++;
16030d621f6SSteven Price 	}
1618782fb61SSteven Price 	mmap_write_unlock(mm);
16230d621f6SSteven Price 
16330d621f6SSteven Price 	/* Flush out the last page */
164f8f0d0b6SSteven Price 	st->note_page(st, 0, -1, 0);
16530d621f6SSteven Price }
166