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