162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <linux/pagewalk.h> 462306a36Sopenharmony_ci#include <linux/ptdump.h> 562306a36Sopenharmony_ci#include <linux/kasan.h> 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS) 862306a36Sopenharmony_ci/* 962306a36Sopenharmony_ci * This is an optimization for KASAN=y case. Since all kasan page tables 1062306a36Sopenharmony_ci * eventually point to the kasan_early_shadow_page we could call note_page() 1162306a36Sopenharmony_ci * right away without walking through lower level page tables. This saves 1262306a36Sopenharmony_ci * us dozens of seconds (minutes for 5-level config) while checking for 1362306a36Sopenharmony_ci * W+X mapping or reading kernel_page_tables debugfs file. 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_cistatic inline int note_kasan_page_table(struct mm_walk *walk, 1662306a36Sopenharmony_ci unsigned long addr) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci struct ptdump_state *st = walk->private; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci st->note_page(st, addr, 4, pte_val(kasan_early_shadow_pte[0])); 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci walk->action = ACTION_CONTINUE; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci return 0; 2562306a36Sopenharmony_ci} 2662306a36Sopenharmony_ci#endif 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic int ptdump_pgd_entry(pgd_t *pgd, unsigned long addr, 2962306a36Sopenharmony_ci unsigned long next, struct mm_walk *walk) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci struct ptdump_state *st = walk->private; 3262306a36Sopenharmony_ci pgd_t val = READ_ONCE(*pgd); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#if CONFIG_PGTABLE_LEVELS > 4 && \ 3562306a36Sopenharmony_ci (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) 3662306a36Sopenharmony_ci if (pgd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_p4d))) 3762306a36Sopenharmony_ci return note_kasan_page_table(walk, addr); 3862306a36Sopenharmony_ci#endif 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci if (st->effective_prot) 4162306a36Sopenharmony_ci st->effective_prot(st, 0, pgd_val(val)); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci if (pgd_leaf(val)) { 4462306a36Sopenharmony_ci st->note_page(st, addr, 0, pgd_val(val)); 4562306a36Sopenharmony_ci walk->action = ACTION_CONTINUE; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci return 0; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic int ptdump_p4d_entry(p4d_t *p4d, unsigned long addr, 5262306a36Sopenharmony_ci unsigned long next, struct mm_walk *walk) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct ptdump_state *st = walk->private; 5562306a36Sopenharmony_ci p4d_t val = READ_ONCE(*p4d); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci#if CONFIG_PGTABLE_LEVELS > 3 && \ 5862306a36Sopenharmony_ci (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) 5962306a36Sopenharmony_ci if (p4d_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pud))) 6062306a36Sopenharmony_ci return note_kasan_page_table(walk, addr); 6162306a36Sopenharmony_ci#endif 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (st->effective_prot) 6462306a36Sopenharmony_ci st->effective_prot(st, 1, p4d_val(val)); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (p4d_leaf(val)) { 6762306a36Sopenharmony_ci st->note_page(st, addr, 1, p4d_val(val)); 6862306a36Sopenharmony_ci walk->action = ACTION_CONTINUE; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci return 0; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic int ptdump_pud_entry(pud_t *pud, unsigned long addr, 7562306a36Sopenharmony_ci unsigned long next, struct mm_walk *walk) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct ptdump_state *st = walk->private; 7862306a36Sopenharmony_ci pud_t val = READ_ONCE(*pud); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci#if CONFIG_PGTABLE_LEVELS > 2 && \ 8162306a36Sopenharmony_ci (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) 8262306a36Sopenharmony_ci if (pud_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pmd))) 8362306a36Sopenharmony_ci return note_kasan_page_table(walk, addr); 8462306a36Sopenharmony_ci#endif 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (st->effective_prot) 8762306a36Sopenharmony_ci st->effective_prot(st, 2, pud_val(val)); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (pud_leaf(val)) { 9062306a36Sopenharmony_ci st->note_page(st, addr, 2, pud_val(val)); 9162306a36Sopenharmony_ci walk->action = ACTION_CONTINUE; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int ptdump_pmd_entry(pmd_t *pmd, unsigned long addr, 9862306a36Sopenharmony_ci unsigned long next, struct mm_walk *walk) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct ptdump_state *st = walk->private; 10162306a36Sopenharmony_ci pmd_t val = READ_ONCE(*pmd); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS) 10462306a36Sopenharmony_ci if (pmd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pte))) 10562306a36Sopenharmony_ci return note_kasan_page_table(walk, addr); 10662306a36Sopenharmony_ci#endif 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (st->effective_prot) 10962306a36Sopenharmony_ci st->effective_prot(st, 3, pmd_val(val)); 11062306a36Sopenharmony_ci if (pmd_leaf(val)) { 11162306a36Sopenharmony_ci st->note_page(st, addr, 3, pmd_val(val)); 11262306a36Sopenharmony_ci walk->action = ACTION_CONTINUE; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int ptdump_pte_entry(pte_t *pte, unsigned long addr, 11962306a36Sopenharmony_ci unsigned long next, struct mm_walk *walk) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci struct ptdump_state *st = walk->private; 12262306a36Sopenharmony_ci pte_t val = ptep_get_lockless(pte); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci if (st->effective_prot) 12562306a36Sopenharmony_ci st->effective_prot(st, 4, pte_val(val)); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci st->note_page(st, addr, 4, pte_val(val)); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci return 0; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic int ptdump_hole(unsigned long addr, unsigned long next, 13362306a36Sopenharmony_ci int depth, struct mm_walk *walk) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct ptdump_state *st = walk->private; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci st->note_page(st, addr, depth, 0); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return 0; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic const struct mm_walk_ops ptdump_ops = { 14362306a36Sopenharmony_ci .pgd_entry = ptdump_pgd_entry, 14462306a36Sopenharmony_ci .p4d_entry = ptdump_p4d_entry, 14562306a36Sopenharmony_ci .pud_entry = ptdump_pud_entry, 14662306a36Sopenharmony_ci .pmd_entry = ptdump_pmd_entry, 14762306a36Sopenharmony_ci .pte_entry = ptdump_pte_entry, 14862306a36Sopenharmony_ci .pte_hole = ptdump_hole, 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_civoid ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm, pgd_t *pgd) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci const struct ptdump_range *range = st->range; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci mmap_write_lock(mm); 15662306a36Sopenharmony_ci while (range->start != range->end) { 15762306a36Sopenharmony_ci walk_page_range_novma(mm, range->start, range->end, 15862306a36Sopenharmony_ci &ptdump_ops, pgd, st); 15962306a36Sopenharmony_ci range++; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci mmap_write_unlock(mm); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* Flush out the last page */ 16462306a36Sopenharmony_ci st->note_page(st, 0, -1, 0); 16562306a36Sopenharmony_ci} 166