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