linux/arch/arm/mm/dump.c
<<
>>
Prefs
   1/*
   2 * Debug helper to dump the current kernel pagetables of the system
   3 * so that we can see what the various memory ranges are set to.
   4 *
   5 * Derived from x86 implementation:
   6 * (C) Copyright 2008 Intel Corporation
   7 *
   8 * Author: Arjan van de Ven <arjan@linux.intel.com>
   9 *
  10 * This program is free software; you can redistribute it and/or
  11 * modify it under the terms of the GNU General Public License
  12 * as published by the Free Software Foundation; version 2
  13 * of the License.
  14 */
  15#include <linux/debugfs.h>
  16#include <linux/fs.h>
  17#include <linux/mm.h>
  18#include <linux/seq_file.h>
  19
  20#include <asm/domain.h>
  21#include <asm/fixmap.h>
  22#include <asm/memory.h>
  23#include <asm/pgtable.h>
  24#include <asm/ptdump.h>
  25
  26static struct addr_marker address_markers[] = {
  27        { MODULES_VADDR,        "Modules" },
  28        { PAGE_OFFSET,          "Kernel Mapping" },
  29        { 0,                    "vmalloc() Area" },
  30        { VMALLOC_END,          "vmalloc() End" },
  31        { FIXADDR_START,        "Fixmap Area" },
  32        { VECTORS_BASE, "Vectors" },
  33        { VECTORS_BASE + PAGE_SIZE * 2, "Vectors End" },
  34        { -1,                   NULL },
  35};
  36
  37#define pt_dump_seq_printf(m, fmt, args...) \
  38({                      \
  39        if (m)                                  \
  40                seq_printf(m, fmt, ##args);     \
  41})
  42
  43#define pt_dump_seq_puts(m, fmt)    \
  44({                                              \
  45        if (m)                                  \
  46                seq_printf(m, fmt);     \
  47})
  48
  49struct pg_state {
  50        struct seq_file *seq;
  51        const struct addr_marker *marker;
  52        unsigned long start_address;
  53        unsigned level;
  54        u64 current_prot;
  55        bool check_wx;
  56        unsigned long wx_pages;
  57        const char *current_domain;
  58};
  59
  60struct prot_bits {
  61        u64             mask;
  62        u64             val;
  63        const char      *set;
  64        const char      *clear;
  65        bool            ro_bit;
  66        bool            nx_bit;
  67};
  68
  69static const struct prot_bits pte_bits[] = {
  70        {
  71                .mask   = L_PTE_USER,
  72                .val    = L_PTE_USER,
  73                .set    = "USR",
  74                .clear  = "   ",
  75        }, {
  76                .mask   = L_PTE_RDONLY,
  77                .val    = L_PTE_RDONLY,
  78                .set    = "ro",
  79                .clear  = "RW",
  80                .ro_bit = true,
  81        }, {
  82                .mask   = L_PTE_XN,
  83                .val    = L_PTE_XN,
  84                .set    = "NX",
  85                .clear  = "x ",
  86                .nx_bit = true,
  87        }, {
  88                .mask   = L_PTE_SHARED,
  89                .val    = L_PTE_SHARED,
  90                .set    = "SHD",
  91                .clear  = "   ",
  92        }, {
  93                .mask   = L_PTE_MT_MASK,
  94                .val    = L_PTE_MT_UNCACHED,
  95                .set    = "SO/UNCACHED",
  96        }, {
  97                .mask   = L_PTE_MT_MASK,
  98                .val    = L_PTE_MT_BUFFERABLE,
  99                .set    = "MEM/BUFFERABLE/WC",
 100        }, {
 101                .mask   = L_PTE_MT_MASK,
 102                .val    = L_PTE_MT_WRITETHROUGH,
 103                .set    = "MEM/CACHED/WT",
 104        }, {
 105                .mask   = L_PTE_MT_MASK,
 106                .val    = L_PTE_MT_WRITEBACK,
 107                .set    = "MEM/CACHED/WBRA",
 108#ifndef CONFIG_ARM_LPAE
 109        }, {
 110                .mask   = L_PTE_MT_MASK,
 111                .val    = L_PTE_MT_MINICACHE,
 112                .set    = "MEM/MINICACHE",
 113#endif
 114        }, {
 115                .mask   = L_PTE_MT_MASK,
 116                .val    = L_PTE_MT_WRITEALLOC,
 117                .set    = "MEM/CACHED/WBWA",
 118        }, {
 119                .mask   = L_PTE_MT_MASK,
 120                .val    = L_PTE_MT_DEV_SHARED,
 121                .set    = "DEV/SHARED",
 122#ifndef CONFIG_ARM_LPAE
 123        }, {
 124                .mask   = L_PTE_MT_MASK,
 125                .val    = L_PTE_MT_DEV_NONSHARED,
 126                .set    = "DEV/NONSHARED",
 127#endif
 128        }, {
 129                .mask   = L_PTE_MT_MASK,
 130                .val    = L_PTE_MT_DEV_WC,
 131                .set    = "DEV/WC",
 132        }, {
 133                .mask   = L_PTE_MT_MASK,
 134                .val    = L_PTE_MT_DEV_CACHED,
 135                .set    = "DEV/CACHED",
 136        },
 137};
 138
 139static const struct prot_bits section_bits[] = {
 140#ifdef CONFIG_ARM_LPAE
 141        {
 142                .mask   = PMD_SECT_USER,
 143                .val    = PMD_SECT_USER,
 144                .set    = "USR",
 145        }, {
 146                .mask   = L_PMD_SECT_RDONLY | PMD_SECT_AP2,
 147                .val    = L_PMD_SECT_RDONLY | PMD_SECT_AP2,
 148                .set    = "ro",
 149                .clear  = "RW",
 150                .ro_bit = true,
 151#elif __LINUX_ARM_ARCH__ >= 6
 152        {
 153                .mask   = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 154                .val    = PMD_SECT_APX | PMD_SECT_AP_WRITE,
 155                .set    = "    ro",
 156                .ro_bit = true,
 157        }, {
 158                .mask   = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 159                .val    = PMD_SECT_AP_WRITE,
 160                .set    = "    RW",
 161        }, {
 162                .mask   = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 163                .val    = PMD_SECT_AP_READ,
 164                .set    = "USR ro",
 165        }, {
 166                .mask   = PMD_SECT_APX | PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 167                .val    = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 168                .set    = "USR RW",
 169#else /* ARMv4/ARMv5  */
 170        /* These are approximate */
 171        {
 172                .mask   = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 173                .val    = 0,
 174                .set    = "    ro",
 175                .ro_bit = true,
 176        }, {
 177                .mask   = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 178                .val    = PMD_SECT_AP_WRITE,
 179                .set    = "    RW",
 180        }, {
 181                .mask   = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 182                .val    = PMD_SECT_AP_READ,
 183                .set    = "USR ro",
 184        }, {
 185                .mask   = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 186                .val    = PMD_SECT_AP_READ | PMD_SECT_AP_WRITE,
 187                .set    = "USR RW",
 188#endif
 189        }, {
 190                .mask   = PMD_SECT_XN,
 191                .val    = PMD_SECT_XN,
 192                .set    = "NX",
 193                .clear  = "x ",
 194                .nx_bit = true,
 195        }, {
 196                .mask   = PMD_SECT_S,
 197                .val    = PMD_SECT_S,
 198                .set    = "SHD",
 199                .clear  = "   ",
 200        },
 201};
 202
 203struct pg_level {
 204        const struct prot_bits *bits;
 205        size_t num;
 206        u64 mask;
 207        const struct prot_bits *ro_bit;
 208        const struct prot_bits *nx_bit;
 209};
 210
 211static struct pg_level pg_level[] = {
 212        {
 213        }, { /* pgd */
 214        }, { /* pud */
 215        }, { /* pmd */
 216                .bits   = section_bits,
 217                .num    = ARRAY_SIZE(section_bits),
 218        }, { /* pte */
 219                .bits   = pte_bits,
 220                .num    = ARRAY_SIZE(pte_bits),
 221        },
 222};
 223
 224static void dump_prot(struct pg_state *st, const struct prot_bits *bits, size_t num)
 225{
 226        unsigned i;
 227
 228        for (i = 0; i < num; i++, bits++) {
 229                const char *s;
 230
 231                if ((st->current_prot & bits->mask) == bits->val)
 232                        s = bits->set;
 233                else
 234                        s = bits->clear;
 235
 236                if (s)
 237                        pt_dump_seq_printf(st->seq, " %s", s);
 238        }
 239}
 240
 241static void note_prot_wx(struct pg_state *st, unsigned long addr)
 242{
 243        if (!st->check_wx)
 244                return;
 245        if ((st->current_prot & pg_level[st->level].ro_bit->mask) ==
 246                                pg_level[st->level].ro_bit->val)
 247                return;
 248        if ((st->current_prot & pg_level[st->level].nx_bit->mask) ==
 249                                pg_level[st->level].nx_bit->val)
 250                return;
 251
 252        WARN_ONCE(1, "arm/mm: Found insecure W+X mapping at address %pS\n",
 253                        (void *)st->start_address);
 254
 255        st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
 256}
 257
 258static void note_page(struct pg_state *st, unsigned long addr,
 259                      unsigned int level, u64 val, const char *domain)
 260{
 261        static const char units[] = "KMGTPE";
 262        u64 prot = val & pg_level[level].mask;
 263
 264        if (!st->level) {
 265                st->level = level;
 266                st->current_prot = prot;
 267                st->current_domain = domain;
 268                pt_dump_seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
 269        } else if (prot != st->current_prot || level != st->level ||
 270                   domain != st->current_domain ||
 271                   addr >= st->marker[1].start_address) {
 272                const char *unit = units;
 273                unsigned long delta;
 274
 275                if (st->current_prot) {
 276                        note_prot_wx(st, addr);
 277                        pt_dump_seq_printf(st->seq, "0x%08lx-0x%08lx   ",
 278                                   st->start_address, addr);
 279
 280                        delta = (addr - st->start_address) >> 10;
 281                        while (!(delta & 1023) && unit[1]) {
 282                                delta >>= 10;
 283                                unit++;
 284                        }
 285                        pt_dump_seq_printf(st->seq, "%9lu%c", delta, *unit);
 286                        if (st->current_domain)
 287                                pt_dump_seq_printf(st->seq, " %s",
 288                                                        st->current_domain);
 289                        if (pg_level[st->level].bits)
 290                                dump_prot(st, pg_level[st->level].bits, pg_level[st->level].num);
 291                        pt_dump_seq_printf(st->seq, "\n");
 292                }
 293
 294                if (addr >= st->marker[1].start_address) {
 295                        st->marker++;
 296                        pt_dump_seq_printf(st->seq, "---[ %s ]---\n",
 297                                                        st->marker->name);
 298                }
 299                st->start_address = addr;
 300                st->current_prot = prot;
 301                st->current_domain = domain;
 302                st->level = level;
 303        }
 304}
 305
 306static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start,
 307                     const char *domain)
 308{
 309        pte_t *pte = pte_offset_kernel(pmd, 0);
 310        unsigned long addr;
 311        unsigned i;
 312
 313        for (i = 0; i < PTRS_PER_PTE; i++, pte++) {
 314                addr = start + i * PAGE_SIZE;
 315                note_page(st, addr, 4, pte_val(*pte), domain);
 316        }
 317}
 318
 319static const char *get_domain_name(pmd_t *pmd)
 320{
 321#ifndef CONFIG_ARM_LPAE
 322        switch (pmd_val(*pmd) & PMD_DOMAIN_MASK) {
 323        case PMD_DOMAIN(DOMAIN_KERNEL):
 324                return "KERNEL ";
 325        case PMD_DOMAIN(DOMAIN_USER):
 326                return "USER   ";
 327        case PMD_DOMAIN(DOMAIN_IO):
 328                return "IO     ";
 329        case PMD_DOMAIN(DOMAIN_VECTORS):
 330                return "VECTORS";
 331        default:
 332                return "unknown";
 333        }
 334#endif
 335        return NULL;
 336}
 337
 338static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start)
 339{
 340        pmd_t *pmd = pmd_offset(pud, 0);
 341        unsigned long addr;
 342        unsigned i;
 343        const char *domain;
 344
 345        for (i = 0; i < PTRS_PER_PMD; i++, pmd++) {
 346                addr = start + i * PMD_SIZE;
 347                domain = get_domain_name(pmd);
 348                if (pmd_none(*pmd) || pmd_large(*pmd) || !pmd_present(*pmd))
 349                        note_page(st, addr, 3, pmd_val(*pmd), domain);
 350                else
 351                        walk_pte(st, pmd, addr, domain);
 352
 353                if (SECTION_SIZE < PMD_SIZE && pmd_large(pmd[1])) {
 354                        addr += SECTION_SIZE;
 355                        pmd++;
 356                        domain = get_domain_name(pmd);
 357                        note_page(st, addr, 3, pmd_val(*pmd), domain);
 358                }
 359        }
 360}
 361
 362static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start)
 363{
 364        pud_t *pud = pud_offset(pgd, 0);
 365        unsigned long addr;
 366        unsigned i;
 367
 368        for (i = 0; i < PTRS_PER_PUD; i++, pud++) {
 369                addr = start + i * PUD_SIZE;
 370                if (!pud_none(*pud)) {
 371                        walk_pmd(st, pud, addr);
 372                } else {
 373                        note_page(st, addr, 2, pud_val(*pud), NULL);
 374                }
 375        }
 376}
 377
 378static void walk_pgd(struct pg_state *st, struct mm_struct *mm,
 379                        unsigned long start)
 380{
 381        pgd_t *pgd = pgd_offset(mm, 0UL);
 382        unsigned i;
 383        unsigned long addr;
 384
 385        for (i = 0; i < PTRS_PER_PGD; i++, pgd++) {
 386                addr = start + i * PGDIR_SIZE;
 387                if (!pgd_none(*pgd)) {
 388                        walk_pud(st, pgd, addr);
 389                } else {
 390                        note_page(st, addr, 1, pgd_val(*pgd), NULL);
 391                }
 392        }
 393}
 394
 395void ptdump_walk_pgd(struct seq_file *m, struct ptdump_info *info)
 396{
 397        struct pg_state st = {
 398                .seq = m,
 399                .marker = info->markers,
 400                .check_wx = false,
 401        };
 402
 403        walk_pgd(&st, info->mm, info->base_addr);
 404        note_page(&st, 0, 0, 0, NULL);
 405}
 406
 407static void ptdump_initialize(void)
 408{
 409        unsigned i, j;
 410
 411        for (i = 0; i < ARRAY_SIZE(pg_level); i++)
 412                if (pg_level[i].bits)
 413                        for (j = 0; j < pg_level[i].num; j++) {
 414                                pg_level[i].mask |= pg_level[i].bits[j].mask;
 415                                if (pg_level[i].bits[j].ro_bit)
 416                                        pg_level[i].ro_bit = &pg_level[i].bits[j];
 417                                if (pg_level[i].bits[j].nx_bit)
 418                                        pg_level[i].nx_bit = &pg_level[i].bits[j];
 419                        }
 420
 421        address_markers[2].start_address = VMALLOC_START;
 422}
 423
 424static struct ptdump_info kernel_ptdump_info = {
 425        .mm = &init_mm,
 426        .markers = address_markers,
 427        .base_addr = 0,
 428};
 429
 430void ptdump_check_wx(void)
 431{
 432        struct pg_state st = {
 433                .seq = NULL,
 434                .marker = (struct addr_marker[]) {
 435                        { 0, NULL},
 436                        { -1, NULL},
 437                },
 438                .check_wx = true,
 439        };
 440
 441        walk_pgd(&st, &init_mm, 0);
 442        note_page(&st, 0, 0, 0, NULL);
 443        if (st.wx_pages)
 444                pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n",
 445                        st.wx_pages);
 446        else
 447                pr_info("Checked W+X mappings: passed, no W+X pages found\n");
 448}
 449
 450static int ptdump_init(void)
 451{
 452        ptdump_initialize();
 453        return ptdump_debugfs_register(&kernel_ptdump_info,
 454                                        "kernel_page_tables");
 455}
 456__initcall(ptdump_init);
 457