File Index
Files and directories
Sign in
FILE

ptdump.c

File preview with syntax highlighting.

linux-6.19.14/arch/arm64/mm/ptdump.c C
  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
 41static const struct ptdump_prot_bits pte_bits[] = {
 42	{
 43		.mask	= PTE_VALID,
 44		.val	= PTE_VALID,
 45		.set	= " ",
 46		.clear	= "F",
 47	}, {
 48		.mask	= PTE_USER,
 49		.val	= PTE_USER,
 50		.set	= "USR",
 51		.clear	= "   ",
 52	}, {
 53		.mask	= PTE_RDONLY,
 54		.val	= PTE_RDONLY,
 55		.set	= "ro",
 56		.clear	= "RW",
 57	}, {
 58		.mask	= PTE_PXN,
 59		.val	= PTE_PXN,
 60		.set	= "NX",
 61		.clear	= "x ",
 62	}, {
 63		.mask	= PTE_SHARED,
 64		.val	= PTE_SHARED,
 65		.set	= "SHD",
 66		.clear	= "   ",
 67	}, {
 68		.mask	= PTE_AF,
 69		.val	= PTE_AF,
 70		.set	= "AF",
 71		.clear	= "  ",
 72	}, {
 73		.mask	= PTE_NG,
 74		.val	= PTE_NG,
 75		.set	= "NG",
 76		.clear	= "  ",
 77	}, {
 78		.mask	= PTE_CONT,
 79		.val	= PTE_CONT,
 80		.set	= "CON",
 81		.clear	= "   ",
 82	}, {
 83		.mask	= PMD_TYPE_MASK,
 84		.val	= PMD_TYPE_SECT,
 85		.set	= "BLK",
 86		.clear	= "   ",
 87	}, {
 88		.mask	= PTE_UXN,
 89		.val	= PTE_UXN,
 90		.set	= "UXN",
 91		.clear	= "   ",
 92	}, {
 93		.mask	= PTE_GP,
 94		.val	= PTE_GP,
 95		.set	= "GP",
 96		.clear	= "  ",
 97	}, {
 98		.mask	= PTE_ATTRINDX_MASK,
 99		.val	= PTE_ATTRINDX(MT_DEVICE_nGnRnE),
100		.set	= "DEVICE/nGnRnE",
101	}, {
102		.mask	= PTE_ATTRINDX_MASK,
103		.val	= PTE_ATTRINDX(MT_DEVICE_nGnRE),
104		.set	= "DEVICE/nGnRE",
105	}, {
106		.mask	= PTE_ATTRINDX_MASK,
107		.val	= PTE_ATTRINDX(MT_NORMAL_NC),
108		.set	= "MEM/NORMAL-NC",
109	}, {
110		.mask	= PTE_ATTRINDX_MASK,
111		.val	= PTE_ATTRINDX(MT_NORMAL),
112		.set	= "MEM/NORMAL",
113	}, {
114		.mask	= PTE_ATTRINDX_MASK,
115		.val	= PTE_ATTRINDX(MT_NORMAL_TAGGED),
116		.set	= "MEM/NORMAL-TAGGED",
117	}
118};
119
120static struct ptdump_pg_level kernel_pg_levels[] __ro_after_init = {
121	{ /* pgd */
122		.name	= "PGD",
123		.bits	= pte_bits,
124		.num	= ARRAY_SIZE(pte_bits),
125	}, { /* p4d */
126		.name	= "P4D",
127		.bits	= pte_bits,
128		.num	= ARRAY_SIZE(pte_bits),
129	}, { /* pud */
130		.name	= "PUD",
131		.bits	= pte_bits,
132		.num	= ARRAY_SIZE(pte_bits),
133	}, { /* pmd */
134		.name	= "PMD",
135		.bits	= pte_bits,
136		.num	= ARRAY_SIZE(pte_bits),
137	}, { /* pte */
138		.name	= "PTE",
139		.bits	= pte_bits,
140		.num	= ARRAY_SIZE(pte_bits),
141	},
142};
143
144static void dump_prot(struct ptdump_pg_state *st, const struct ptdump_prot_bits *bits,
145			size_t num)
146{
147	unsigned i;
148
149	for (i = 0; i < num; i++, bits++) {
150		const char *s;
151
152		if ((st->current_prot & bits->mask) == bits->val)
153			s = bits->set;
154		else
155			s = bits->clear;
156
157		if (s)
158			pt_dump_seq_printf(st->seq, " %s", s);
159	}
160}
161
162static void note_prot_uxn(struct ptdump_pg_state *st, unsigned long addr)
163{
164	if (!st->check_wx)
165		return;
166
167	if ((st->current_prot & PTE_UXN) == PTE_UXN)
168		return;
169
170	WARN_ONCE(1, "arm64/mm: Found non-UXN mapping at address %p/%pS\n",
171		  (void *)st->start_address, (void *)st->start_address);
172
173	st->uxn_pages += (addr - st->start_address) / PAGE_SIZE;
174}
175
176static void note_prot_wx(struct ptdump_pg_state *st, unsigned long addr)
177{
178	if (!st->check_wx)
179		return;
180	if ((st->current_prot & PTE_RDONLY) == PTE_RDONLY)
181		return;
182	if ((st->current_prot & PTE_PXN) == PTE_PXN)
183		return;
184
185	WARN_ONCE(1, "arm64/mm: Found insecure W+X mapping at address %p/%pS\n",
186		  (void *)st->start_address, (void *)st->start_address);
187
188	st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
189}
190
191void note_page(struct ptdump_state *pt_st, unsigned long addr, int level,
192	       pteval_t val)
193{
194	struct ptdump_pg_state *st = container_of(pt_st, struct ptdump_pg_state, ptdump);
195	struct ptdump_pg_level *pg_level = st->pg_level;
196	static const char units[] = "KMGTPE";
197	ptdesc_t prot = 0;
198
199	/* check if the current level has been folded dynamically */
200	if (st->mm && ((level == 1 && mm_p4d_folded(st->mm)) ||
201	    (level == 2 && mm_pud_folded(st->mm))))
202		level = 0;
203
204	if (level >= 0)
205		prot = val & pg_level[level].mask;
206
207	if (st->level == -1) {
208		st->level = level;
209		st->current_prot = prot;
210		st->start_address = addr;
211		pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
212	} else if (prot != st->current_prot || level != st->level ||
213		   addr >= st->marker[1].start_address) {
214		const char *unit = units;
215		unsigned long delta;
216
217		if (st->current_prot) {
218			note_prot_uxn(st, addr);
219			note_prot_wx(st, addr);
220		}
221
222		pt_dump_seq_printf(st->seq, "0x%016lx-0x%016lx   ",
223				   st->start_address, addr);
224
225		delta = (addr - st->start_address) >> 10;
226		while (!(delta & 1023) && unit[1]) {
227			delta >>= 10;
228			unit++;
229		}
230		pt_dump_seq_printf(st->seq, "%9lu%c %s", delta, *unit,
231				   pg_level[st->level].name);
232		if (st->current_prot && pg_level[st->level].bits)
233			dump_prot(st, pg_level[st->level].bits,
234				  pg_level[st->level].num);
235		pt_dump_seq_puts(st->seq, "\n");
236
237		if (addr >= st->marker[1].start_address) {
238			st->marker++;
239			pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
240		}
241
242		st->start_address = addr;
243		st->current_prot = prot;
244		st->level = level;
245	}
246
247	if (addr >= st->marker[1].start_address) {
248		st->marker++;
249		pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
250	}
251
252}
253
254void note_page_pte(struct ptdump_state *pt_st, unsigned long addr, pte_t pte)
255{
256	note_page(pt_st, addr, 4, pte_val(pte));
257}
258
259void note_page_pmd(struct ptdump_state *pt_st, unsigned long addr, pmd_t pmd)
260{
261	note_page(pt_st, addr, 3, pmd_val(pmd));
262}
263
264void note_page_pud(struct ptdump_state *pt_st, unsigned long addr, pud_t pud)
265{
266	note_page(pt_st, addr, 2, pud_val(pud));
267}
268
269void note_page_p4d(struct ptdump_state *pt_st, unsigned long addr, p4d_t p4d)
270{
271	note_page(pt_st, addr, 1, p4d_val(p4d));
272}
273
274void note_page_pgd(struct ptdump_state *pt_st, unsigned long addr, pgd_t pgd)
275{
276	note_page(pt_st, addr, 0, pgd_val(pgd));
277}
278
279void note_page_flush(struct ptdump_state *pt_st)
280{
281	pte_t pte_zero = {0};
282
283	note_page(pt_st, 0, -1, pte_val(pte_zero));
284}
285
286static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
287{
288	static_branch_inc(&arm64_ptdump_lock_key);
289	ptdump_walk_pgd(st, mm, NULL);
290	static_branch_dec(&arm64_ptdump_lock_key);
291}
292
293void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
294{
295	unsigned long end = ~0UL;
296	struct ptdump_pg_state st;
297
298	if (info->base_addr < TASK_SIZE_64)
299		end = TASK_SIZE_64;
300
301	st = (struct ptdump_pg_state){
302		.seq = s,
303		.marker = info->markers,
304		.mm = info->mm,
305		.pg_level = &kernel_pg_levels[0],
306		.level = -1,
307		.ptdump = {
308			.note_page_pte = note_page_pte,
309			.note_page_pmd = note_page_pmd,
310			.note_page_pud = note_page_pud,
311			.note_page_p4d = note_page_p4d,
312			.note_page_pgd = note_page_pgd,
313			.note_page_flush = note_page_flush,
314			.range = (struct ptdump_range[]){
315				{info->base_addr, end},
316				{0, 0}
317			}
318		}
319	};
320
321	arm64_ptdump_walk_pgd(&st.ptdump, info->mm);
322}
323
324static void __init ptdump_initialize(void)
325{
326	unsigned i, j;
327
328	for (i = 0; i < ARRAY_SIZE(kernel_pg_levels); i++)
329		if (kernel_pg_levels[i].bits)
330			for (j = 0; j < kernel_pg_levels[i].num; j++)
331				kernel_pg_levels[i].mask |= kernel_pg_levels[i].bits[j].mask;
332}
333
334static struct ptdump_info kernel_ptdump_info __ro_after_init = {
335	.mm		= &init_mm,
336};
337
338bool ptdump_check_wx(void)
339{
340	struct ptdump_pg_state st = {
341		.seq = NULL,
342		.marker = (struct addr_marker[]) {
343			{ 0, NULL},
344			{ -1, NULL},
345		},
346		.pg_level = &kernel_pg_levels[0],
347		.level = -1,
348		.check_wx = true,
349		.ptdump = {
350			.note_page_pte = note_page_pte,
351			.note_page_pmd = note_page_pmd,
352			.note_page_pud = note_page_pud,
353			.note_page_p4d = note_page_p4d,
354			.note_page_pgd = note_page_pgd,
355			.note_page_flush = note_page_flush,
356			.range = (struct ptdump_range[]) {
357				{_PAGE_OFFSET(vabits_actual), ~0UL},
358				{0, 0}
359			}
360		}
361	};
362
363	arm64_ptdump_walk_pgd(&st.ptdump, &init_mm);
364
365	if (st.wx_pages || st.uxn_pages) {
366		pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
367			st.wx_pages, st.uxn_pages);
368
369		return false;
370	} else {
371		pr_info("Checked W+X mappings: passed, no W+X pages found\n");
372
373		return true;
374	}
375}
376
377static int __init ptdump_init(void)
378{
379	u64 page_offset = _PAGE_OFFSET(vabits_actual);
380	u64 vmemmap_start = (u64)virt_to_page((void *)page_offset);
381	struct addr_marker m[] = {
382		{ PAGE_OFFSET,		"Linear Mapping start" },
383		{ PAGE_END,		"Linear Mapping end" },
384#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
385		{ KASAN_SHADOW_START,   "Kasan shadow start" },
386		{ KASAN_SHADOW_END,     "Kasan shadow end" },
387#endif
388		{ MODULES_VADDR,	"Modules start" },
389		{ MODULES_END,		"Modules end" },
390		{ VMALLOC_START,	"vmalloc() area" },
391		{ VMALLOC_END,		"vmalloc() end" },
392		{ vmemmap_start,	"vmemmap start" },
393		{ VMEMMAP_END,		"vmemmap end" },
394		{ PCI_IO_START,		"PCI I/O start" },
395		{ PCI_IO_END,		"PCI I/O end" },
396		{ FIXADDR_TOT_START,    "Fixmap start" },
397		{ FIXADDR_TOP,	        "Fixmap end" },
398		{ -1,			NULL },
399	};
400	static struct addr_marker address_markers[ARRAY_SIZE(m)] __ro_after_init;
401
402	kernel_ptdump_info.markers = memcpy(address_markers, m, sizeof(m));
403	kernel_ptdump_info.base_addr = page_offset;
404
405	ptdump_initialize();
406	ptdump_debugfs_register(&kernel_ptdump_info, "kernel_page_tables");
407	return 0;
408}
409device_initcall(ptdump_init);
~/ Read-only file preview.