xref: /openbmc/linux/arch/riscv/mm/ptdump.c (revision 55fd7e02)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2019 SiFive
4  */
5 
6 #include <linux/init.h>
7 #include <linux/debugfs.h>
8 #include <linux/seq_file.h>
9 #include <linux/ptdump.h>
10 
11 #include <asm/ptdump.h>
12 #include <linux/pgtable.h>
13 #include <asm/kasan.h>
14 
15 #define pt_dump_seq_printf(m, fmt, args...)	\
16 ({						\
17 	if (m)					\
18 		seq_printf(m, fmt, ##args);	\
19 })
20 
21 #define pt_dump_seq_puts(m, fmt)	\
22 ({					\
23 	if (m)				\
24 		seq_printf(m, fmt);	\
25 })
26 
27 /*
28  * The page dumper groups page table entries of the same type into a single
29  * description. It uses pg_state to track the range information while
30  * iterating over the pte entries. When the continuity is broken it then
31  * dumps out a description of the range.
32  */
33 struct pg_state {
34 	struct ptdump_state ptdump;
35 	struct seq_file *seq;
36 	const struct addr_marker *marker;
37 	unsigned long start_address;
38 	unsigned long start_pa;
39 	unsigned long last_pa;
40 	int level;
41 	u64 current_prot;
42 	bool check_wx;
43 	unsigned long wx_pages;
44 };
45 
46 /* Address marker */
47 struct addr_marker {
48 	unsigned long start_address;
49 	const char *name;
50 };
51 
52 static struct addr_marker address_markers[] = {
53 #ifdef CONFIG_KASAN
54 	{KASAN_SHADOW_START,	"Kasan shadow start"},
55 	{KASAN_SHADOW_END,	"Kasan shadow end"},
56 #endif
57 	{FIXADDR_START,		"Fixmap start"},
58 	{FIXADDR_TOP,		"Fixmap end"},
59 	{PCI_IO_START,		"PCI I/O start"},
60 	{PCI_IO_END,		"PCI I/O end"},
61 #ifdef CONFIG_SPARSEMEM_VMEMMAP
62 	{VMEMMAP_START,		"vmemmap start"},
63 	{VMEMMAP_END,		"vmemmap end"},
64 #endif
65 	{VMALLOC_START,		"vmalloc() area"},
66 	{VMALLOC_END,		"vmalloc() end"},
67 	{PAGE_OFFSET,		"Linear mapping"},
68 	{-1, NULL},
69 };
70 
71 /* Page Table Entry */
72 struct prot_bits {
73 	u64 mask;
74 	u64 val;
75 	const char *set;
76 	const char *clear;
77 };
78 
79 static const struct prot_bits pte_bits[] = {
80 	{
81 		.mask = _PAGE_SOFT,
82 		.val = _PAGE_SOFT,
83 		.set = "RSW",
84 		.clear = "   ",
85 	}, {
86 		.mask = _PAGE_DIRTY,
87 		.val = _PAGE_DIRTY,
88 		.set = "D",
89 		.clear = ".",
90 	}, {
91 		.mask = _PAGE_ACCESSED,
92 		.val = _PAGE_ACCESSED,
93 		.set = "A",
94 		.clear = ".",
95 	}, {
96 		.mask = _PAGE_GLOBAL,
97 		.val = _PAGE_GLOBAL,
98 		.set = "G",
99 		.clear = ".",
100 	}, {
101 		.mask = _PAGE_USER,
102 		.val = _PAGE_USER,
103 		.set = "U",
104 		.clear = ".",
105 	}, {
106 		.mask = _PAGE_EXEC,
107 		.val = _PAGE_EXEC,
108 		.set = "X",
109 		.clear = ".",
110 	}, {
111 		.mask = _PAGE_WRITE,
112 		.val = _PAGE_WRITE,
113 		.set = "W",
114 		.clear = ".",
115 	}, {
116 		.mask = _PAGE_READ,
117 		.val = _PAGE_READ,
118 		.set = "R",
119 		.clear = ".",
120 	}, {
121 		.mask = _PAGE_PRESENT,
122 		.val = _PAGE_PRESENT,
123 		.set = "V",
124 		.clear = ".",
125 	}
126 };
127 
128 /* Page Level */
129 struct pg_level {
130 	const char *name;
131 	u64 mask;
132 };
133 
134 static struct pg_level pg_level[] = {
135 	{ /* pgd */
136 		.name = "PGD",
137 	}, { /* p4d */
138 		.name = (CONFIG_PGTABLE_LEVELS > 4) ? "P4D" : "PGD",
139 	}, { /* pud */
140 		.name = (CONFIG_PGTABLE_LEVELS > 3) ? "PUD" : "PGD",
141 	}, { /* pmd */
142 		.name = (CONFIG_PGTABLE_LEVELS > 2) ? "PMD" : "PGD",
143 	}, { /* pte */
144 		.name = "PTE",
145 	},
146 };
147 
148 static void dump_prot(struct pg_state *st)
149 {
150 	unsigned int i;
151 
152 	for (i = 0; i < ARRAY_SIZE(pte_bits); i++) {
153 		const char *s;
154 
155 		if ((st->current_prot & pte_bits[i].mask) == pte_bits[i].val)
156 			s = pte_bits[i].set;
157 		else
158 			s = pte_bits[i].clear;
159 
160 		if (s)
161 			pt_dump_seq_printf(st->seq, " %s", s);
162 	}
163 }
164 
165 #ifdef CONFIG_64BIT
166 #define ADDR_FORMAT	"0x%016lx"
167 #else
168 #define ADDR_FORMAT	"0x%08lx"
169 #endif
170 static void dump_addr(struct pg_state *st, unsigned long addr)
171 {
172 	static const char units[] = "KMGTPE";
173 	const char *unit = units;
174 	unsigned long delta;
175 
176 	pt_dump_seq_printf(st->seq, ADDR_FORMAT "-" ADDR_FORMAT "   ",
177 			   st->start_address, addr);
178 
179 	pt_dump_seq_printf(st->seq, " " ADDR_FORMAT " ", st->start_pa);
180 	delta = (addr - st->start_address) >> 10;
181 
182 	while (!(delta & 1023) && unit[1]) {
183 		delta >>= 10;
184 		unit++;
185 	}
186 
187 	pt_dump_seq_printf(st->seq, "%9lu%c %s", delta, *unit,
188 			   pg_level[st->level].name);
189 }
190 
191 static void note_prot_wx(struct pg_state *st, unsigned long addr)
192 {
193 	if (!st->check_wx)
194 		return;
195 
196 	if ((st->current_prot & (_PAGE_WRITE | _PAGE_EXEC)) !=
197 	    (_PAGE_WRITE | _PAGE_EXEC))
198 		return;
199 
200 	WARN_ONCE(1, "riscv/mm: Found insecure W+X mapping at address %p/%pS\n",
201 		  (void *)st->start_address, (void *)st->start_address);
202 
203 	st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
204 }
205 
206 static void note_page(struct ptdump_state *pt_st, unsigned long addr,
207 		      int level, u64 val)
208 {
209 	struct pg_state *st = container_of(pt_st, struct pg_state, ptdump);
210 	u64 pa = PFN_PHYS(pte_pfn(__pte(val)));
211 	u64 prot = 0;
212 
213 	if (level >= 0)
214 		prot = val & pg_level[level].mask;
215 
216 	if (st->level == -1) {
217 		st->level = level;
218 		st->current_prot = prot;
219 		st->start_address = addr;
220 		st->start_pa = pa;
221 		st->last_pa = pa;
222 		pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
223 	} else if (prot != st->current_prot ||
224 		   level != st->level || addr >= st->marker[1].start_address) {
225 		if (st->current_prot) {
226 			note_prot_wx(st, addr);
227 			dump_addr(st, addr);
228 			dump_prot(st);
229 			pt_dump_seq_puts(st->seq, "\n");
230 		}
231 
232 		while (addr >= st->marker[1].start_address) {
233 			st->marker++;
234 			pt_dump_seq_printf(st->seq, "---[ %s ]---\n",
235 					   st->marker->name);
236 		}
237 
238 		st->start_address = addr;
239 		st->start_pa = pa;
240 		st->last_pa = pa;
241 		st->current_prot = prot;
242 		st->level = level;
243 	} else {
244 		st->last_pa = pa;
245 	}
246 }
247 
248 static void ptdump_walk(struct seq_file *s)
249 {
250 	struct pg_state st = {
251 		.seq = s,
252 		.marker = address_markers,
253 		.level = -1,
254 		.ptdump = {
255 			.note_page = note_page,
256 			.range = (struct ptdump_range[]) {
257 				{KERN_VIRT_START, ULONG_MAX},
258 				{0, 0}
259 			}
260 		}
261 	};
262 
263 	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
264 }
265 
266 void ptdump_check_wx(void)
267 {
268 	struct pg_state st = {
269 		.seq = NULL,
270 		.marker = (struct addr_marker[]) {
271 			{0, NULL},
272 			{-1, NULL},
273 		},
274 		.level = -1,
275 		.check_wx = true,
276 		.ptdump = {
277 			.note_page = note_page,
278 			.range = (struct ptdump_range[]) {
279 				{KERN_VIRT_START, ULONG_MAX},
280 				{0, 0}
281 			}
282 		}
283 	};
284 
285 	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
286 
287 	if (st.wx_pages)
288 		pr_warn("Checked W+X mappings: failed, %lu W+X pages found\n",
289 			st.wx_pages);
290 	else
291 		pr_info("Checked W+X mappings: passed, no W+X pages found\n");
292 }
293 
294 static int ptdump_show(struct seq_file *m, void *v)
295 {
296 	ptdump_walk(m);
297 
298 	return 0;
299 }
300 
301 DEFINE_SHOW_ATTRIBUTE(ptdump);
302 
303 static int ptdump_init(void)
304 {
305 	unsigned int i, j;
306 
307 	for (i = 0; i < ARRAY_SIZE(pg_level); i++)
308 		for (j = 0; j < ARRAY_SIZE(pte_bits); j++)
309 			pg_level[i].mask |= pte_bits[j].mask;
310 
311 	debugfs_create_file("kernel_page_tables", 0400, NULL, NULL,
312 			    &ptdump_fops);
313 
314 	return 0;
315 }
316 
317 device_initcall(ptdump_init);
318