1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (C) 2019 SiFive
6 #include <linux/init.h>
7 #include <linux/debugfs.h>
8 #include <linux/seq_file.h>
9 #include <linux/ptdump.h>
11 #include <asm/ptdump.h>
12 #include <asm/pgtable.h>
13 #include <asm/kasan.h>
15 #define pt_dump_seq_printf(m, fmt, args...) \
18 seq_printf(m, fmt, ##args); \
21 #define pt_dump_seq_puts(m, fmt) \
28 * The page dumper groups page table entries of the same type into a single
29 * description. It uses pg_state to track the range information while
30 * iterating over the pte entries. When the continuity is broken it then
31 * dumps out a description of the range.
34 struct ptdump_state ptdump
;
36 const struct addr_marker
*marker
;
37 unsigned long start_address
;
38 unsigned long start_pa
;
39 unsigned long last_pa
;
43 unsigned long wx_pages
;
48 unsigned long start_address
;
52 static struct addr_marker address_markers
[] = {
54 {KASAN_SHADOW_START
, "Kasan shadow start"},
55 {KASAN_SHADOW_END
, "Kasan shadow end"},
57 {FIXADDR_START
, "Fixmap start"},
58 {FIXADDR_TOP
, "Fixmap end"},
59 {PCI_IO_START
, "PCI I/O start"},
60 {PCI_IO_END
, "PCI I/O end"},
61 #ifdef CONFIG_SPARSEMEM_VMEMMAP
62 {VMEMMAP_START
, "vmemmap start"},
63 {VMEMMAP_END
, "vmemmap end"},
65 {VMALLOC_START
, "vmalloc() area"},
66 {VMALLOC_END
, "vmalloc() end"},
67 {PAGE_OFFSET
, "Linear mapping"},
71 /* Page Table Entry */
79 static const struct prot_bits pte_bits
[] = {
91 .mask
= _PAGE_ACCESSED
,
92 .val
= _PAGE_ACCESSED
,
121 .mask
= _PAGE_PRESENT
,
122 .val
= _PAGE_PRESENT
,
134 static struct pg_level pg_level
[] = {
138 .name
= (CONFIG_PGTABLE_LEVELS
> 4) ? "P4D" : "PGD",
140 .name
= (CONFIG_PGTABLE_LEVELS
> 3) ? "PUD" : "PGD",
142 .name
= (CONFIG_PGTABLE_LEVELS
> 2) ? "PMD" : "PGD",
148 static void dump_prot(struct pg_state
*st
)
152 for (i
= 0; i
< ARRAY_SIZE(pte_bits
); i
++) {
155 if ((st
->current_prot
& pte_bits
[i
].mask
) == pte_bits
[i
].val
)
158 s
= pte_bits
[i
].clear
;
161 pt_dump_seq_printf(st
->seq
, " %s", s
);
166 #define ADDR_FORMAT "0x%016lx"
168 #define ADDR_FORMAT "0x%08lx"
170 static void dump_addr(struct pg_state
*st
, unsigned long addr
)
172 static const char units
[] = "KMGTPE";
173 const char *unit
= units
;
176 pt_dump_seq_printf(st
->seq
, ADDR_FORMAT
"-" ADDR_FORMAT
" ",
177 st
->start_address
, addr
);
179 pt_dump_seq_printf(st
->seq
, " " ADDR_FORMAT
" ", st
->start_pa
);
180 delta
= (addr
- st
->start_address
) >> 10;
182 while (!(delta
& 1023) && unit
[1]) {
187 pt_dump_seq_printf(st
->seq
, "%9lu%c %s", delta
, *unit
,
188 pg_level
[st
->level
].name
);
191 static void note_prot_wx(struct pg_state
*st
, unsigned long addr
)
196 if ((st
->current_prot
& (_PAGE_WRITE
| _PAGE_EXEC
)) !=
197 (_PAGE_WRITE
| _PAGE_EXEC
))
200 WARN_ONCE(1, "riscv/mm: Found insecure W+X mapping at address %p/%pS\n",
201 (void *)st
->start_address
, (void *)st
->start_address
);
203 st
->wx_pages
+= (addr
- st
->start_address
) / PAGE_SIZE
;
206 static void note_page(struct ptdump_state
*pt_st
, unsigned long addr
,
207 int level
, unsigned long val
)
209 struct pg_state
*st
= container_of(pt_st
, struct pg_state
, ptdump
);
210 u64 pa
= PFN_PHYS(pte_pfn(__pte(val
)));
214 prot
= val
& pg_level
[level
].mask
;
216 if (st
->level
== -1) {
218 st
->current_prot
= prot
;
219 st
->start_address
= addr
;
222 pt_dump_seq_printf(st
->seq
, "---[ %s ]---\n", st
->marker
->name
);
223 } else if (prot
!= st
->current_prot
||
224 level
!= st
->level
|| addr
>= st
->marker
[1].start_address
) {
225 if (st
->current_prot
) {
226 note_prot_wx(st
, addr
);
229 pt_dump_seq_puts(st
->seq
, "\n");
232 while (addr
>= st
->marker
[1].start_address
) {
234 pt_dump_seq_printf(st
->seq
, "---[ %s ]---\n",
238 st
->start_address
= addr
;
241 st
->current_prot
= prot
;
248 static void ptdump_walk(struct seq_file
*s
)
250 struct pg_state st
= {
252 .marker
= address_markers
,
255 .note_page
= note_page
,
256 .range
= (struct ptdump_range
[]) {
257 {KERN_VIRT_START
, ULONG_MAX
},
263 ptdump_walk_pgd(&st
.ptdump
, &init_mm
, NULL
);
266 void ptdump_check_wx(void)
268 struct pg_state st
= {
270 .marker
= (struct addr_marker
[]) {
277 .note_page
= note_page
,
278 .range
= (struct ptdump_range
[]) {
279 {KERN_VIRT_START
, ULONG_MAX
},
285 ptdump_walk_pgd(&st
.ptdump
, &init_mm
, NULL
);
288 pr_warn("Checked W+X mappings: failed, %lu W+X pages found\n",
291 pr_info("Checked W+X mappings: passed, no W+X pages found\n");
294 static int ptdump_show(struct seq_file
*m
, void *v
)
301 DEFINE_SHOW_ATTRIBUTE(ptdump
);
303 static int ptdump_init(void)
307 for (i
= 0; i
< ARRAY_SIZE(pg_level
); i
++)
308 for (j
= 0; j
< ARRAY_SIZE(pte_bits
); j
++)
309 pg_level
[i
].mask
|= pte_bits
[j
].mask
;
311 debugfs_create_file("kernel_page_tables", 0400, NULL
, NULL
,
317 device_initcall(ptdump_init
);