xref: /linux/arch/arm64/mm/ptdump.c (revision da5b2ad1c2f18834cb1ce429e2e5a5cf5cbdf21b)
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(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, pg_level[st->level].bits,
266 				  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.ptdump, 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.ptdump, &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