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