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 */
47struct 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
60struct prot_bits {
61 u64 mask;
62 u64 val;
63 const char *set;
64 const char *clear;
65};
66
67static 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
146struct pg_level {
147 const struct prot_bits *bits;
148 char name[4];
149 int num;
150 u64 mask;
151};
152
153static 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
177static 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
195static 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
209static 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
224static 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
286void 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
311static 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
321static struct ptdump_info kernel_ptdump_info __ro_after_init = {
322 .mm = &init_mm,
323};
324
325bool 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
358static 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}
390device_initcall(ptdump_init);
391

source code of linux/arch/arm64/mm/ptdump.c