cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ptdump.c (8733B)


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