1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Debug helper to dump the current kernel pagetables of the system |
4 | * so that we can see what the various memory ranges are set to. |
5 | * |
6 | * Derived from x86 implementation: |
7 | * (C) Copyright 2008 Intel Corporation |
8 | * |
9 | * Author: Arjan van de Ven <arjan@linux.intel.com> |
10 | */ |
11 | #include <linux/debugfs.h> |
12 | #include <linux/fs.h> |
13 | #include <linux/mm.h> |
14 | #include <linux/seq_file.h> |
15 | |
16 | #include <asm/domain.h> |
17 | #include <asm/fixmap.h> |
18 | #include <asm/page.h> |
19 | #include <asm/ptdump.h> |
20 | |
21 | static struct addr_marker address_markers[] = { |
22 | #ifdef CONFIG_KASAN |
23 | { KASAN_SHADOW_START, "Kasan shadow start" }, |
24 | { KASAN_SHADOW_END, "Kasan shadow end" }, |
25 | #endif |
26 | { MODULES_VADDR, "Modules" }, |
27 | { PAGE_OFFSET, "Kernel Mapping" }, |
28 | { 0, "vmalloc() Area" }, |
29 | { FDT_FIXED_BASE, "FDT Area" }, |
30 | { FIXADDR_START, "Fixmap Area" }, |
31 | { VECTORS_BASE, "Vectors" }, |
32 | { VECTORS_BASE + PAGE_SIZE * 2, "Vectors End" }, |
33 | { -1, NULL }, |
34 | }; |
35 | |
36 | #define pt_dump_seq_printf(m, fmt, args...) \ |
37 | ({ \ |
38 | if (m) \ |
39 | seq_printf(m, fmt, ##args); \ |
40 | }) |
41 | |
42 | #define pt_dump_seq_puts(m, fmt) \ |
43 | ({ \ |
44 | if (m) \ |
45 | seq_printf(m, fmt); \ |
46 | }) |
47 | |
48 | struct pg_state { |
49 | struct seq_file *seq; |
50 | const struct addr_marker *marker; |
51 | unsigned long start_address; |
52 | unsigned level; |
53 | u64 current_prot; |
54 | bool check_wx; |
55 | unsigned long wx_pages; |
56 | const char *current_domain; |
57 | }; |
58 | |
59 | struct prot_bits { |
60 | u64 mask; |
61 | u64 val; |
62 | const char *set; |
63 | const char *clear; |
64 | bool ro_bit; |
65 | bool nx_bit; |
66 | }; |
67 | |
68 | static const struct prot_bits pte_bits[] = { |
69 | { |
70 | .mask = L_PTE_USER, |
71 | .val = L_PTE_USER, |
72 | .set = "USR" , |
73 | .clear = " " , |
74 | }, { |
75 | .mask = L_PTE_RDONLY, |
76 | .val = L_PTE_RDONLY, |
77 | .set = "ro" , |
78 | .clear = "RW" , |
79 | .ro_bit = true, |
80 | }, { |
81 | .mask = L_PTE_XN, |
82 | .val = L_PTE_XN, |
83 | .set = "NX" , |
84 | .clear = "x " , |
85 | .nx_bit = true, |
86 | }, { |
87 | .mask = L_PTE_SHARED, |
88 | .val = L_PTE_SHARED, |
89 | .set = "SHD" , |
90 | .clear = " " , |
91 | }, { |
92 | .mask = L_PTE_MT_MASK, |
93 | .val = L_PTE_MT_UNCACHED, |
94 | .set = "SO/UNCACHED" , |
95 | }, { |
96 | .mask = L_PTE_MT_MASK, |
97 | .val = L_PTE_MT_BUFFERABLE, |
98 | .set = "MEM/BUFFERABLE/WC" , |
99 | }, { |
100 | .mask = L_PTE_MT_MASK, |
101 | .val = L_PTE_MT_WRITETHROUGH, |
102 | .set = "MEM/CACHED/WT" , |
103 | }, { |
104 | .mask = L_PTE_MT_MASK, |
105 | .val = L_PTE_MT_WRITEBACK, |
106 | .set = "MEM/CACHED/WBRA" , |
107 | #ifndef CONFIG_ARM_LPAE |
108 | }, { |
109 | .mask = L_PTE_MT_MASK, |
110 | .val = L_PTE_MT_MINICACHE, |
111 | .set = "MEM/MINICACHE" , |
112 | #endif |
113 | }, { |
114 | .mask = L_PTE_MT_MASK, |
115 | .val = L_PTE_MT_WRITEALLOC, |
116 | .set = "MEM/CACHED/WBWA" , |
117 | }, { |
118 | .mask = L_PTE_MT_MASK, |
119 | .val = L_PTE_MT_DEV_SHARED, |
120 | .set = "DEV/SHARED" , |
121 | #ifndef CONFIG_ARM_LPAE |
122 | }, { |
123 | .mask = L_PTE_MT_MASK, |
124 | .val = L_PTE_MT_DEV_NONSHARED, |
125 | .set = "DEV/NONSHARED" , |
126 | #endif |
127 | }, { |
128 | .mask = L_PTE_MT_MASK, |
129 | .val = L_PTE_MT_DEV_WC, |
130 | .set = "DEV/WC" , |
131 | }, { |
132 | .mask = L_PTE_MT_MASK, |
133 | .val = L_PTE_MT_DEV_CACHED, |
134 | .set = "DEV/CACHED" , |
135 | }, |
136 | }; |
137 | |
138 | static const struct prot_bits section_bits[] = { |
139 | #ifdef CONFIG_ARM_LPAE |
140 | { |
141 | .mask = PMD_SECT_USER, |
142 | .val = PMD_SECT_USER, |
143 | .set = "USR" , |
144 | }, { |
145 | .mask = L_PMD_SECT_RDONLY | PMD_SECT_AP2, |
146 | .val = L_PMD_SECT_RDONLY | PMD_SECT_AP2, |
147 | .set = "ro" , |
148 | .clear = "RW" , |
149 | .ro_bit = true, |
150 | #elif __LINUX_ARM_ARCH__ >= 6 |
151 | { |
152 | .mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
153 | .val = PMD_SECT_APX | PMD_SECT_AP_WRITE, |
154 | .set = " ro" , |
155 | .ro_bit = true, |
156 | }, { |
157 | .mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
158 | .val = PMD_SECT_AP_WRITE, |
159 | .set = " RW" , |
160 | }, { |
161 | .mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
162 | .val = PMD_SECT_AP_READ, |
163 | .set = "USR ro" , |
164 | }, { |
165 | .mask = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
166 | .val = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
167 | .set = "USR RW" , |
168 | #else /* ARMv4/ARMv5 */ |
169 | /* These are approximate */ |
170 | { |
171 | .mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
172 | .val = 0, |
173 | .set = " ro" , |
174 | .ro_bit = true, |
175 | }, { |
176 | .mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
177 | .val = PMD_SECT_AP_WRITE, |
178 | .set = " RW" , |
179 | }, { |
180 | .mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
181 | .val = PMD_SECT_AP_READ, |
182 | .set = "USR ro" , |
183 | }, { |
184 | .mask = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
185 | .val = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE, |
186 | .set = "USR RW" , |
187 | #endif |
188 | }, { |
189 | .mask = PMD_SECT_XN, |
190 | .val = PMD_SECT_XN, |
191 | .set = "NX" , |
192 | .clear = "x " , |
193 | .nx_bit = true, |
194 | }, { |
195 | .mask = PMD_SECT_S, |
196 | .val = PMD_SECT_S, |
197 | .set = "SHD" , |
198 | .clear = " " , |
199 | }, |
200 | }; |
201 | |
202 | struct pg_level { |
203 | const char *name; |
204 | const struct prot_bits *bits; |
205 | size_t num; |
206 | u64 mask; |
207 | const struct prot_bits *ro_bit; |
208 | const struct prot_bits *nx_bit; |
209 | }; |
210 | |
211 | static struct pg_level pg_level[] = { |
212 | { |
213 | }, { /* pgd */ |
214 | }, { /* p4d */ |
215 | }, { /* pud */ |
216 | }, { /* pmd */ |
217 | .name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD" , |
218 | .bits = section_bits, |
219 | .num = ARRAY_SIZE(section_bits), |
220 | }, { /* pte */ |
221 | .name = "PTE" , |
222 | .bits = pte_bits, |
223 | .num = ARRAY_SIZE(pte_bits), |
224 | }, |
225 | }; |
226 | |
227 | static void dump_prot(struct pg_state *st, const struct prot_bits *bits, size_t num) |
228 | { |
229 | unsigned i; |
230 | |
231 | for (i = 0; i < num; i++, bits++) { |
232 | const char *s; |
233 | |
234 | if ((st->current_prot & bits->mask) == bits->val) |
235 | s = bits->set; |
236 | else |
237 | s = bits->clear; |
238 | |
239 | if (s) |
240 | pt_dump_seq_printf(st->seq, " %s" , s); |
241 | } |
242 | } |
243 | |
244 | static void note_prot_wx(struct pg_state *st, unsigned long addr) |
245 | { |
246 | if (!st->check_wx) |
247 | return; |
248 | if ((st->current_prot & pg_level[st->level].ro_bit->mask) == |
249 | pg_level[st->level].ro_bit->val) |
250 | return; |
251 | if ((st->current_prot & pg_level[st->level].nx_bit->mask) == |
252 | pg_level[st->level].nx_bit->val) |
253 | return; |
254 | |
255 | WARN_ONCE(1, "arm/mm: Found insecure W+X mapping at address %pS\n" , |
256 | (void *)st->start_address); |
257 | |
258 | st->wx_pages += (addr - st->start_address) / PAGE_SIZE; |
259 | } |
260 | |
261 | static void note_page(struct pg_state *st, unsigned long addr, |
262 | unsigned int level, u64 val, const char *domain) |
263 | { |
264 | static const char units[] = "KMGTPE" ; |
265 | u64 prot = val & pg_level[level].mask; |
266 | |
267 | if (!st->level) { |
268 | st->level = level; |
269 | st->current_prot = prot; |
270 | st->current_domain = domain; |
271 | pt_dump_seq_printf(st->seq, "---[ %s ]---\n" , st->marker->name); |
272 | } else if (prot != st->current_prot || level != st->level || |
273 | domain != st->current_domain || |
274 | addr >= st->marker[1].start_address) { |
275 | const char *unit = units; |
276 | unsigned long delta; |
277 | |
278 | if (st->current_prot) { |
279 | note_prot_wx(st, addr); |
280 | pt_dump_seq_printf(st->seq, "0x%08lx-0x%08lx " , |
281 | st->start_address, addr); |
282 | |
283 | delta = (addr - st->start_address) >> 10; |
284 | while (!(delta & 1023) && unit[1]) { |
285 | delta >>= 10; |
286 | unit++; |
287 | } |
288 | pt_dump_seq_printf(st->seq, "%9lu%c %s" , delta, *unit, |
289 | pg_level[st->level].name); |
290 | if (st->current_domain) |
291 | pt_dump_seq_printf(st->seq, " %s" , |
292 | st->current_domain); |
293 | if (pg_level[st->level].bits) |
294 | dump_prot(st, bits: pg_level[st->level].bits, num: pg_level[st->level].num); |
295 | pt_dump_seq_printf(st->seq, "\n" ); |
296 | } |
297 | |
298 | if (addr >= st->marker[1].start_address) { |
299 | st->marker++; |
300 | pt_dump_seq_printf(st->seq, "---[ %s ]---\n" , |
301 | st->marker->name); |
302 | } |
303 | st->start_address = addr; |
304 | st->current_prot = prot; |
305 | st->current_domain = domain; |
306 | st->level = level; |
307 | } |
308 | } |
309 | |
310 | static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start, |
311 | const char *domain) |
312 | { |
313 | pte_t *pte = pte_offset_kernel(pmd, address: 0); |
314 | unsigned long addr; |
315 | unsigned i; |
316 | |
317 | for (i = 0; i < PTRS_PER_PTE; i++, pte++) { |
318 | addr = start + i * PAGE_SIZE; |
319 | note_page(st, addr, level: 5, val: pte_val(pte: *pte), domain); |
320 | } |
321 | } |
322 | |
323 | static const char *get_domain_name(pmd_t *pmd) |
324 | { |
325 | #ifndef CONFIG_ARM_LPAE |
326 | switch (pmd_val(*pmd) & PMD_DOMAIN_MASK) { |
327 | case PMD_DOMAIN(DOMAIN_KERNEL): |
328 | return "KERNEL " ; |
329 | case PMD_DOMAIN(DOMAIN_USER): |
330 | return "USER " ; |
331 | case PMD_DOMAIN(DOMAIN_IO): |
332 | return "IO " ; |
333 | case PMD_DOMAIN(DOMAIN_VECTORS): |
334 | return "VECTORS" ; |
335 | default: |
336 | return "unknown" ; |
337 | } |
338 | #endif |
339 | return NULL; |
340 | } |
341 | |
342 | static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start) |
343 | { |
344 | pmd_t *pmd = pmd_offset(pud, address: 0); |
345 | unsigned long addr; |
346 | unsigned i; |
347 | const char *domain; |
348 | |
349 | for (i = 0; i < PTRS_PER_PMD; i++, pmd++) { |
350 | addr = start + i * PMD_SIZE; |
351 | domain = get_domain_name(pmd); |
352 | if (pmd_none(pmd: *pmd) || pmd_leaf(pte: *pmd) || !pmd_present(pmd: *pmd)) |
353 | note_page(st, addr, level: 4, val: pmd_val(pmd: *pmd), domain); |
354 | else |
355 | walk_pte(st, pmd, start: addr, domain); |
356 | |
357 | if (SECTION_SIZE < PMD_SIZE && pmd_leaf(pmd[1])) { |
358 | addr += SECTION_SIZE; |
359 | pmd++; |
360 | domain = get_domain_name(pmd); |
361 | note_page(st, addr, level: 4, val: pmd_val(pmd: *pmd), domain); |
362 | } |
363 | } |
364 | } |
365 | |
366 | static void walk_pud(struct pg_state *st, p4d_t *p4d, unsigned long start) |
367 | { |
368 | pud_t *pud = pud_offset(p4d, address: 0); |
369 | unsigned long addr; |
370 | unsigned i; |
371 | |
372 | for (i = 0; i < PTRS_PER_PUD; i++, pud++) { |
373 | addr = start + i * PUD_SIZE; |
374 | if (!pud_none(pud: *pud)) { |
375 | walk_pmd(st, pud, start: addr); |
376 | } else { |
377 | note_page(st, addr, level: 3, val: pud_val(pud: *pud), NULL); |
378 | } |
379 | } |
380 | } |
381 | |
382 | static void walk_p4d(struct pg_state *st, pgd_t *pgd, unsigned long start) |
383 | { |
384 | p4d_t *p4d = p4d_offset(pgd, address: 0); |
385 | unsigned long addr; |
386 | unsigned i; |
387 | |
388 | for (i = 0; i < PTRS_PER_P4D; i++, p4d++) { |
389 | addr = start + i * P4D_SIZE; |
390 | if (!p4d_none(p4d: *p4d)) { |
391 | walk_pud(st, p4d, start: addr); |
392 | } else { |
393 | note_page(st, addr, level: 2, val: p4d_val(p4d: *p4d), NULL); |
394 | } |
395 | } |
396 | } |
397 | |
398 | static void walk_pgd(struct pg_state *st, struct mm_struct *mm, |
399 | unsigned long start) |
400 | { |
401 | pgd_t *pgd = pgd_offset(mm, 0UL); |
402 | unsigned i; |
403 | unsigned long addr; |
404 | |
405 | for (i = 0; i < PTRS_PER_PGD; i++, pgd++) { |
406 | addr = start + i * PGDIR_SIZE; |
407 | if (!pgd_none(pgd: *pgd)) { |
408 | walk_p4d(st, pgd, start: addr); |
409 | } else { |
410 | note_page(st, addr, level: 1, val: pgd_val(pgd: *pgd), NULL); |
411 | } |
412 | } |
413 | } |
414 | |
415 | void ptdump_walk_pgd(struct seq_file *m, struct ptdump_info *info) |
416 | { |
417 | struct pg_state st = { |
418 | .seq = m, |
419 | .marker = info->markers, |
420 | .check_wx = false, |
421 | }; |
422 | |
423 | walk_pgd(st: &st, mm: info->mm, start: info->base_addr); |
424 | note_page(st: &st, addr: 0, level: 0, val: 0, NULL); |
425 | } |
426 | |
427 | static void __init ptdump_initialize(void) |
428 | { |
429 | unsigned i, j; |
430 | |
431 | for (i = 0; i < ARRAY_SIZE(pg_level); i++) |
432 | if (pg_level[i].bits) |
433 | for (j = 0; j < pg_level[i].num; j++) { |
434 | pg_level[i].mask |= pg_level[i].bits[j].mask; |
435 | if (pg_level[i].bits[j].ro_bit) |
436 | pg_level[i].ro_bit = &pg_level[i].bits[j]; |
437 | if (pg_level[i].bits[j].nx_bit) |
438 | pg_level[i].nx_bit = &pg_level[i].bits[j]; |
439 | } |
440 | #ifdef CONFIG_KASAN |
441 | address_markers[4].start_address = VMALLOC_START; |
442 | #else |
443 | address_markers[2].start_address = VMALLOC_START; |
444 | #endif |
445 | } |
446 | |
447 | static struct ptdump_info kernel_ptdump_info = { |
448 | .mm = &init_mm, |
449 | .markers = address_markers, |
450 | .base_addr = 0, |
451 | }; |
452 | |
453 | void ptdump_check_wx(void) |
454 | { |
455 | struct pg_state st = { |
456 | .seq = NULL, |
457 | .marker = (struct addr_marker[]) { |
458 | { 0, NULL}, |
459 | { -1, NULL}, |
460 | }, |
461 | .check_wx = true, |
462 | }; |
463 | |
464 | walk_pgd(st: &st, mm: &init_mm, start: 0); |
465 | note_page(st: &st, addr: 0, level: 0, val: 0, NULL); |
466 | if (st.wx_pages) |
467 | pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n" , |
468 | st.wx_pages); |
469 | else |
470 | pr_info("Checked W+X mappings: passed, no W+X pages found\n" ); |
471 | } |
472 | |
473 | static int __init ptdump_init(void) |
474 | { |
475 | ptdump_initialize(); |
476 | ptdump_debugfs_register(&kernel_ptdump_info, "kernel_page_tables" ); |
477 | return 0; |
478 | } |
479 | __initcall(ptdump_init); |
480 | |