]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - arch/x86/mm/dump_pagetables.c
Merge remote-tracking branch 'at91/at91-next'
[karo-tx-linux.git] / arch / x86 / mm / dump_pagetables.c
index f0cedf3395af465bc65c3cadc49f02bbc68fad48..1bf417e9cc1309e3353f08fee9dad6915fb34c0c 100644 (file)
@@ -32,6 +32,8 @@ struct pg_state {
        const struct addr_marker *marker;
        unsigned long lines;
        bool to_dmesg;
+       bool check_wx;
+       unsigned long wx_pages;
 };
 
 struct addr_marker {
@@ -155,7 +157,7 @@ static void printk_prot(struct seq_file *m, pgprot_t prot, int level, bool dmsg)
                        pt_dump_cont_printf(m, dmsg, "    ");
                if ((level == 4 && pr & _PAGE_PAT) ||
                    ((level == 3 || level == 2) && pr & _PAGE_PAT_LARGE))
-                       pt_dump_cont_printf(m, dmsg, "pat ");
+                       pt_dump_cont_printf(m, dmsg, "PAT ");
                else
                        pt_dump_cont_printf(m, dmsg, "    ");
                if (pr & _PAGE_GLOBAL)
@@ -198,8 +200,8 @@ static void note_page(struct seq_file *m, struct pg_state *st,
         * we have now. "break" is either changing perms, levels or
         * address space marker.
         */
-       prot = pgprot_val(new_prot) & PTE_FLAGS_MASK;
-       cur = pgprot_val(st->current_prot) & PTE_FLAGS_MASK;
+       prot = pgprot_val(new_prot);
+       cur = pgprot_val(st->current_prot);
 
        if (!st->level) {
                /* First entry */
@@ -214,6 +216,16 @@ static void note_page(struct seq_file *m, struct pg_state *st,
                const char *unit = units;
                unsigned long delta;
                int width = sizeof(unsigned long) * 2;
+               pgprotval_t pr = pgprot_val(st->current_prot);
+
+               if (st->check_wx && (pr & _PAGE_RW) && !(pr & _PAGE_NX)) {
+                       WARN_ONCE(1,
+                                 "x86/mm: Found insecure W+X mapping at address %p/%pS\n",
+                                 (void *)st->start_address,
+                                 (void *)st->start_address);
+                       st->wx_pages += (st->current_address -
+                                        st->start_address) / PAGE_SIZE;
+               }
 
                /*
                 * Now print the actual finished series
@@ -269,13 +281,13 @@ static void walk_pte_level(struct seq_file *m, struct pg_state *st, pmd_t addr,
 {
        int i;
        pte_t *start;
+       pgprotval_t prot;
 
        start = (pte_t *) pmd_page_vaddr(addr);
        for (i = 0; i < PTRS_PER_PTE; i++) {
-               pgprot_t prot = pte_pgprot(*start);
-
+               prot = pte_flags(*start);
                st->current_address = normalize_addr(P + i * PTE_LEVEL_MULT);
-               note_page(m, st, prot, 4);
+               note_page(m, st, __pgprot(prot), 4);
                start++;
        }
 }
@@ -287,18 +299,19 @@ static void walk_pmd_level(struct seq_file *m, struct pg_state *st, pud_t addr,
 {
        int i;
        pmd_t *start;
+       pgprotval_t prot;
 
        start = (pmd_t *) pud_page_vaddr(addr);
        for (i = 0; i < PTRS_PER_PMD; i++) {
                st->current_address = normalize_addr(P + i * PMD_LEVEL_MULT);
                if (!pmd_none(*start)) {
-                       pgprotval_t prot = pmd_val(*start) & PTE_FLAGS_MASK;
-
-                       if (pmd_large(*start) || !pmd_present(*start))
+                       if (pmd_large(*start) || !pmd_present(*start)) {
+                               prot = pmd_flags(*start);
                                note_page(m, st, __pgprot(prot), 3);
-                       else
+                       } else {
                                walk_pte_level(m, st, *start,
                                               P + i * PMD_LEVEL_MULT);
+                       }
                } else
                        note_page(m, st, __pgprot(0), 3);
                start++;
@@ -318,19 +331,20 @@ static void walk_pud_level(struct seq_file *m, struct pg_state *st, pgd_t addr,
 {
        int i;
        pud_t *start;
+       pgprotval_t prot;
 
        start = (pud_t *) pgd_page_vaddr(addr);
 
        for (i = 0; i < PTRS_PER_PUD; i++) {
                st->current_address = normalize_addr(P + i * PUD_LEVEL_MULT);
                if (!pud_none(*start)) {
-                       pgprotval_t prot = pud_val(*start) & PTE_FLAGS_MASK;
-
-                       if (pud_large(*start) || !pud_present(*start))
+                       if (pud_large(*start) || !pud_present(*start)) {
+                               prot = pud_flags(*start);
                                note_page(m, st, __pgprot(prot), 2);
-                       else
+                       } else {
                                walk_pmd_level(m, st, *start,
                                               P + i * PUD_LEVEL_MULT);
+                       }
                } else
                        note_page(m, st, __pgprot(0), 2);
 
@@ -344,13 +358,15 @@ static void walk_pud_level(struct seq_file *m, struct pg_state *st, pgd_t addr,
 #define pgd_none(a)  pud_none(__pud(pgd_val(a)))
 #endif
 
-void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd)
+static void ptdump_walk_pgd_level_core(struct seq_file *m, pgd_t *pgd,
+                                      bool checkwx)
 {
 #ifdef CONFIG_X86_64
        pgd_t *start = (pgd_t *) &init_level4_pgt;
 #else
        pgd_t *start = swapper_pg_dir;
 #endif
+       pgprotval_t prot;
        int i;
        struct pg_state st = {};
 
@@ -359,16 +375,20 @@ void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd)
                st.to_dmesg = true;
        }
 
+       st.check_wx = checkwx;
+       if (checkwx)
+               st.wx_pages = 0;
+
        for (i = 0; i < PTRS_PER_PGD; i++) {
                st.current_address = normalize_addr(i * PGD_LEVEL_MULT);
                if (!pgd_none(*start)) {
-                       pgprotval_t prot = pgd_val(*start) & PTE_FLAGS_MASK;
-
-                       if (pgd_large(*start) || !pgd_present(*start))
+                       if (pgd_large(*start) || !pgd_present(*start)) {
+                               prot = pgd_flags(*start);
                                note_page(m, &st, __pgprot(prot), 1);
-                       else
+                       } else {
                                walk_pud_level(m, &st, *start,
                                               i * PGD_LEVEL_MULT);
+                       }
                } else
                        note_page(m, &st, __pgprot(0), 1);
 
@@ -378,8 +398,26 @@ void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd)
        /* Flush out the last page */
        st.current_address = normalize_addr(PTRS_PER_PGD*PGD_LEVEL_MULT);
        note_page(m, &st, __pgprot(0), 0);
+       if (!checkwx)
+               return;
+       if (st.wx_pages)
+               pr_info("x86/mm: Checked W+X mappings: FAILED, %lu W+X pages found.\n",
+                       st.wx_pages);
+       else
+               pr_info("x86/mm: Checked W+X mappings: passed, no W+X pages found.\n");
 }
 
+void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd)
+{
+       ptdump_walk_pgd_level_core(m, pgd, false);
+}
+
+void ptdump_walk_pgd_level_checkwx(void)
+{
+       ptdump_walk_pgd_level_core(NULL, NULL, true);
+}
+
+#ifdef CONFIG_X86_PTDUMP
 static int ptdump_show(struct seq_file *m, void *v)
 {
        ptdump_walk_pgd_level(m, NULL);
@@ -397,10 +435,13 @@ static const struct file_operations ptdump_fops = {
        .llseek         = seq_lseek,
        .release        = single_release,
 };
+#endif
 
 static int pt_dump_init(void)
 {
+#ifdef CONFIG_X86_PTDUMP
        struct dentry *pe;
+#endif
 
 #ifdef CONFIG_X86_32
        /* Not a compile-time constant on x86-32 */
@@ -412,10 +453,12 @@ static int pt_dump_init(void)
        address_markers[FIXADDR_START_NR].start_address = FIXADDR_START;
 #endif
 
+#ifdef CONFIG_X86_PTDUMP
        pe = debugfs_create_file("kernel_page_tables", 0600, NULL, NULL,
                                 &ptdump_fops);
        if (!pe)
                return -ENOMEM;
+#endif
 
        return 0;
 }