Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  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 */
 33struct 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 */
 47struct addr_marker {
 48	unsigned long start_address;
 49	const char *name;
 50};
 51
 52static 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 */
 72struct prot_bits {
 73	u64 mask;
 74	u64 val;
 75	const char *set;
 76	const char *clear;
 77};
 78
 79static 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 */
129struct pg_level {
130	const char *name;
131	u64 mask;
132};
133
134static 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
148static 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
170static 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
191static 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
206static 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
248static 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
266void 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
294static int ptdump_show(struct seq_file *m, void *v)
295{
296	ptdump_walk(m);
297
298	return 0;
299}
300
301DEFINE_SHOW_ATTRIBUTE(ptdump);
302
303static 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
317device_initcall(ptdump_init);