1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2014, The Linux Foundation. All rights reserved. |
4 | * Debug helper to dump the current kernel pagetables of the system |
5 | * so that we can see what the various memory ranges are set to. |
6 | * |
7 | * Derived from x86 and arm implementation: |
8 | * (C) Copyright 2008 Intel Corporation |
9 | * |
10 | * Author: Arjan van de Ven <arjan@linux.intel.com> |
11 | */ |
12 | #include <linux/debugfs.h> |
13 | #include <linux/errno.h> |
14 | #include <linux/fs.h> |
15 | #include <linux/io.h> |
16 | #include <linux/init.h> |
17 | #include <linux/mm.h> |
18 | #include <linux/ptdump.h> |
19 | #include <linux/sched.h> |
20 | #include <linux/seq_file.h> |
21 | |
22 | #include <asm/fixmap.h> |
23 | #include <asm/kasan.h> |
24 | #include <asm/memory.h> |
25 | #include <asm/pgtable-hwdef.h> |
26 | #include <asm/ptdump.h> |
27 | |
28 | |
29 | #define pt_dump_seq_printf(m, fmt, args...) \ |
30 | ({ \ |
31 | if (m) \ |
32 | seq_printf(m, fmt, ##args); \ |
33 | }) |
34 | |
35 | #define pt_dump_seq_puts(m, fmt) \ |
36 | ({ \ |
37 | if (m) \ |
38 | seq_printf(m, fmt); \ |
39 | }) |
40 | |
41 | /* |
42 | * The page dumper groups page table entries of the same type into a single |
43 | * description. It uses pg_state to track the range information while |
44 | * iterating over the pte entries. When the continuity is broken it then |
45 | * dumps out a description of the range. |
46 | */ |
47 | struct pg_state { |
48 | struct ptdump_state ptdump; |
49 | struct seq_file *seq; |
50 | const struct addr_marker *marker; |
51 | const struct mm_struct *mm; |
52 | unsigned long start_address; |
53 | int level; |
54 | u64 current_prot; |
55 | bool check_wx; |
56 | unsigned long wx_pages; |
57 | unsigned long uxn_pages; |
58 | }; |
59 | |
60 | struct prot_bits { |
61 | u64 mask; |
62 | u64 val; |
63 | const char *set; |
64 | const char *clear; |
65 | }; |
66 | |
67 | static const struct prot_bits pte_bits[] = { |
68 | { |
69 | .mask = PTE_VALID, |
70 | .val = PTE_VALID, |
71 | .set = " " , |
72 | .clear = "F" , |
73 | }, { |
74 | .mask = PTE_USER, |
75 | .val = PTE_USER, |
76 | .set = "USR" , |
77 | .clear = " " , |
78 | }, { |
79 | .mask = PTE_RDONLY, |
80 | .val = PTE_RDONLY, |
81 | .set = "ro" , |
82 | .clear = "RW" , |
83 | }, { |
84 | .mask = PTE_PXN, |
85 | .val = PTE_PXN, |
86 | .set = "NX" , |
87 | .clear = "x " , |
88 | }, { |
89 | .mask = PTE_SHARED, |
90 | .val = PTE_SHARED, |
91 | .set = "SHD" , |
92 | .clear = " " , |
93 | }, { |
94 | .mask = PTE_AF, |
95 | .val = PTE_AF, |
96 | .set = "AF" , |
97 | .clear = " " , |
98 | }, { |
99 | .mask = PTE_NG, |
100 | .val = PTE_NG, |
101 | .set = "NG" , |
102 | .clear = " " , |
103 | }, { |
104 | .mask = PTE_CONT, |
105 | .val = PTE_CONT, |
106 | .set = "CON" , |
107 | .clear = " " , |
108 | }, { |
109 | .mask = PTE_TABLE_BIT, |
110 | .val = PTE_TABLE_BIT, |
111 | .set = " " , |
112 | .clear = "BLK" , |
113 | }, { |
114 | .mask = PTE_UXN, |
115 | .val = PTE_UXN, |
116 | .set = "UXN" , |
117 | .clear = " " , |
118 | }, { |
119 | .mask = PTE_GP, |
120 | .val = PTE_GP, |
121 | .set = "GP" , |
122 | .clear = " " , |
123 | }, { |
124 | .mask = PTE_ATTRINDX_MASK, |
125 | .val = PTE_ATTRINDX(MT_DEVICE_nGnRnE), |
126 | .set = "DEVICE/nGnRnE" , |
127 | }, { |
128 | .mask = PTE_ATTRINDX_MASK, |
129 | .val = PTE_ATTRINDX(MT_DEVICE_nGnRE), |
130 | .set = "DEVICE/nGnRE" , |
131 | }, { |
132 | .mask = PTE_ATTRINDX_MASK, |
133 | .val = PTE_ATTRINDX(MT_NORMAL_NC), |
134 | .set = "MEM/NORMAL-NC" , |
135 | }, { |
136 | .mask = PTE_ATTRINDX_MASK, |
137 | .val = PTE_ATTRINDX(MT_NORMAL), |
138 | .set = "MEM/NORMAL" , |
139 | }, { |
140 | .mask = PTE_ATTRINDX_MASK, |
141 | .val = PTE_ATTRINDX(MT_NORMAL_TAGGED), |
142 | .set = "MEM/NORMAL-TAGGED" , |
143 | } |
144 | }; |
145 | |
146 | struct pg_level { |
147 | const struct prot_bits *bits; |
148 | char name[4]; |
149 | int num; |
150 | u64 mask; |
151 | }; |
152 | |
153 | static struct pg_level pg_level[] __ro_after_init = { |
154 | { /* pgd */ |
155 | .name = "PGD" , |
156 | .bits = pte_bits, |
157 | .num = ARRAY_SIZE(pte_bits), |
158 | }, { /* p4d */ |
159 | .name = "P4D" , |
160 | .bits = pte_bits, |
161 | .num = ARRAY_SIZE(pte_bits), |
162 | }, { /* pud */ |
163 | .name = "PUD" , |
164 | .bits = pte_bits, |
165 | .num = ARRAY_SIZE(pte_bits), |
166 | }, { /* pmd */ |
167 | .name = "PMD" , |
168 | .bits = pte_bits, |
169 | .num = ARRAY_SIZE(pte_bits), |
170 | }, { /* pte */ |
171 | .name = "PTE" , |
172 | .bits = pte_bits, |
173 | .num = ARRAY_SIZE(pte_bits), |
174 | }, |
175 | }; |
176 | |
177 | static void dump_prot(struct pg_state *st, const struct prot_bits *bits, |
178 | size_t num) |
179 | { |
180 | unsigned i; |
181 | |
182 | for (i = 0; i < num; i++, bits++) { |
183 | const char *s; |
184 | |
185 | if ((st->current_prot & bits->mask) == bits->val) |
186 | s = bits->set; |
187 | else |
188 | s = bits->clear; |
189 | |
190 | if (s) |
191 | pt_dump_seq_printf(st->seq, " %s" , s); |
192 | } |
193 | } |
194 | |
195 | static void note_prot_uxn(struct pg_state *st, unsigned long addr) |
196 | { |
197 | if (!st->check_wx) |
198 | return; |
199 | |
200 | if ((st->current_prot & PTE_UXN) == PTE_UXN) |
201 | return; |
202 | |
203 | WARN_ONCE(1, "arm64/mm: Found non-UXN mapping at address %p/%pS\n" , |
204 | (void *)st->start_address, (void *)st->start_address); |
205 | |
206 | st->uxn_pages += (addr - st->start_address) / PAGE_SIZE; |
207 | } |
208 | |
209 | static void note_prot_wx(struct pg_state *st, unsigned long addr) |
210 | { |
211 | if (!st->check_wx) |
212 | return; |
213 | if ((st->current_prot & PTE_RDONLY) == PTE_RDONLY) |
214 | return; |
215 | if ((st->current_prot & PTE_PXN) == PTE_PXN) |
216 | return; |
217 | |
218 | WARN_ONCE(1, "arm64/mm: Found insecure W+X mapping at address %p/%pS\n" , |
219 | (void *)st->start_address, (void *)st->start_address); |
220 | |
221 | st->wx_pages += (addr - st->start_address) / PAGE_SIZE; |
222 | } |
223 | |
224 | static void note_page(struct ptdump_state *pt_st, unsigned long addr, int level, |
225 | u64 val) |
226 | { |
227 | struct pg_state *st = container_of(pt_st, struct pg_state, ptdump); |
228 | static const char units[] = "KMGTPE" ; |
229 | u64 prot = 0; |
230 | |
231 | /* check if the current level has been folded dynamically */ |
232 | if ((level == 1 && mm_p4d_folded(mm: st->mm)) || |
233 | (level == 2 && mm_pud_folded(st->mm))) |
234 | level = 0; |
235 | |
236 | if (level >= 0) |
237 | prot = val & pg_level[level].mask; |
238 | |
239 | if (st->level == -1) { |
240 | st->level = level; |
241 | st->current_prot = prot; |
242 | st->start_address = addr; |
243 | pt_dump_seq_printf(st->seq, "---[ %s ]---\n" , st->marker->name); |
244 | } else if (prot != st->current_prot || level != st->level || |
245 | addr >= st->marker[1].start_address) { |
246 | const char *unit = units; |
247 | unsigned long delta; |
248 | |
249 | if (st->current_prot) { |
250 | note_prot_uxn(st, addr); |
251 | note_prot_wx(st, addr); |
252 | } |
253 | |
254 | pt_dump_seq_printf(st->seq, "0x%016lx-0x%016lx " , |
255 | st->start_address, addr); |
256 | |
257 | delta = (addr - st->start_address) >> 10; |
258 | while (!(delta & 1023) && unit[1]) { |
259 | delta >>= 10; |
260 | unit++; |
261 | } |
262 | pt_dump_seq_printf(st->seq, "%9lu%c %s" , delta, *unit, |
263 | pg_level[st->level].name); |
264 | if (st->current_prot && pg_level[st->level].bits) |
265 | dump_prot(st, bits: pg_level[st->level].bits, |
266 | num: pg_level[st->level].num); |
267 | pt_dump_seq_puts(st->seq, "\n" ); |
268 | |
269 | if (addr >= st->marker[1].start_address) { |
270 | st->marker++; |
271 | pt_dump_seq_printf(st->seq, "---[ %s ]---\n" , st->marker->name); |
272 | } |
273 | |
274 | st->start_address = addr; |
275 | st->current_prot = prot; |
276 | st->level = level; |
277 | } |
278 | |
279 | if (addr >= st->marker[1].start_address) { |
280 | st->marker++; |
281 | pt_dump_seq_printf(st->seq, "---[ %s ]---\n" , st->marker->name); |
282 | } |
283 | |
284 | } |
285 | |
286 | void ptdump_walk(struct seq_file *s, struct ptdump_info *info) |
287 | { |
288 | unsigned long end = ~0UL; |
289 | struct pg_state st; |
290 | |
291 | if (info->base_addr < TASK_SIZE_64) |
292 | end = TASK_SIZE_64; |
293 | |
294 | st = (struct pg_state){ |
295 | .seq = s, |
296 | .marker = info->markers, |
297 | .mm = info->mm, |
298 | .level = -1, |
299 | .ptdump = { |
300 | .note_page = note_page, |
301 | .range = (struct ptdump_range[]){ |
302 | {info->base_addr, end}, |
303 | {0, 0} |
304 | } |
305 | } |
306 | }; |
307 | |
308 | ptdump_walk_pgd(st: &st.ptdump, mm: info->mm, NULL); |
309 | } |
310 | |
311 | static void __init ptdump_initialize(void) |
312 | { |
313 | unsigned i, j; |
314 | |
315 | for (i = 0; i < ARRAY_SIZE(pg_level); i++) |
316 | if (pg_level[i].bits) |
317 | for (j = 0; j < pg_level[i].num; j++) |
318 | pg_level[i].mask |= pg_level[i].bits[j].mask; |
319 | } |
320 | |
321 | static struct ptdump_info kernel_ptdump_info __ro_after_init = { |
322 | .mm = &init_mm, |
323 | }; |
324 | |
325 | bool ptdump_check_wx(void) |
326 | { |
327 | struct pg_state st = { |
328 | .seq = NULL, |
329 | .marker = (struct addr_marker[]) { |
330 | { 0, NULL}, |
331 | { -1, NULL}, |
332 | }, |
333 | .level = -1, |
334 | .check_wx = true, |
335 | .ptdump = { |
336 | .note_page = note_page, |
337 | .range = (struct ptdump_range[]) { |
338 | {_PAGE_OFFSET(vabits_actual), ~0UL}, |
339 | {0, 0} |
340 | } |
341 | } |
342 | }; |
343 | |
344 | ptdump_walk_pgd(st: &st.ptdump, mm: &init_mm, NULL); |
345 | |
346 | if (st.wx_pages || st.uxn_pages) { |
347 | pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n" , |
348 | st.wx_pages, st.uxn_pages); |
349 | |
350 | return false; |
351 | } else { |
352 | pr_info("Checked W+X mappings: passed, no W+X pages found\n" ); |
353 | |
354 | return true; |
355 | } |
356 | } |
357 | |
358 | static int __init ptdump_init(void) |
359 | { |
360 | u64 page_offset = _PAGE_OFFSET(vabits_actual); |
361 | u64 vmemmap_start = (u64)virt_to_page((void *)page_offset); |
362 | struct addr_marker m[] = { |
363 | { PAGE_OFFSET, "Linear Mapping start" }, |
364 | { PAGE_END, "Linear Mapping end" }, |
365 | #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS) |
366 | { KASAN_SHADOW_START, "Kasan shadow start" }, |
367 | { KASAN_SHADOW_END, "Kasan shadow end" }, |
368 | #endif |
369 | { MODULES_VADDR, "Modules start" }, |
370 | { MODULES_END, "Modules end" }, |
371 | { VMALLOC_START, "vmalloc() area" }, |
372 | { VMALLOC_END, "vmalloc() end" }, |
373 | { vmemmap_start, "vmemmap start" }, |
374 | { VMEMMAP_END, "vmemmap end" }, |
375 | { PCI_IO_START, "PCI I/O start" }, |
376 | { PCI_IO_END, "PCI I/O end" }, |
377 | { FIXADDR_TOT_START, "Fixmap start" }, |
378 | { FIXADDR_TOP, "Fixmap end" }, |
379 | { -1, NULL }, |
380 | }; |
381 | static struct addr_marker address_markers[ARRAY_SIZE(m)] __ro_after_init; |
382 | |
383 | kernel_ptdump_info.markers = memcpy(address_markers, m, sizeof(m)); |
384 | kernel_ptdump_info.base_addr = page_offset; |
385 | |
386 | ptdump_initialize(); |
387 | ptdump_debugfs_register(&kernel_ptdump_info, "kernel_page_tables" ); |
388 | return 0; |
389 | } |
390 | device_initcall(ptdump_init); |
391 | |