1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/set_memory.h>
3 #include <linux/ptdump.h>
4 #include <linux/seq_file.h>
5 #include <linux/debugfs.h>
7 #include <linux/kasan.h>
8 #include <asm/ptdump.h>
10 #include <asm/sections.h>
12 static unsigned long max_addr
;
15 unsigned long start_address
;
19 enum address_markers_idx
{
20 IDENTITY_BEFORE_NR
= 0,
21 IDENTITY_BEFORE_END_NR
,
25 IDENTITY_AFTER_END_NR
,
27 KASAN_SHADOW_START_NR
,
38 static struct addr_marker address_markers
[] = {
39 [IDENTITY_BEFORE_NR
] = {0, "Identity Mapping Start"},
40 [IDENTITY_BEFORE_END_NR
] = {(unsigned long)_stext
, "Identity Mapping End"},
41 [KERNEL_START_NR
] = {(unsigned long)_stext
, "Kernel Image Start"},
42 [KERNEL_END_NR
] = {(unsigned long)_end
, "Kernel Image End"},
43 [IDENTITY_AFTER_NR
] = {(unsigned long)_end
, "Identity Mapping Start"},
44 [IDENTITY_AFTER_END_NR
] = {0, "Identity Mapping End"},
46 [KASAN_SHADOW_START_NR
] = {KASAN_SHADOW_START
, "Kasan Shadow Start"},
47 [KASAN_SHADOW_END_NR
] = {KASAN_SHADOW_END
, "Kasan Shadow End"},
49 [VMEMMAP_NR
] = {0, "vmemmap Area Start"},
50 [VMEMMAP_END_NR
] = {0, "vmemmap Area End"},
51 [VMALLOC_NR
] = {0, "vmalloc Area Start"},
52 [VMALLOC_END_NR
] = {0, "vmalloc Area End"},
53 [MODULES_NR
] = {0, "Modules Area Start"},
54 [MODULES_END_NR
] = {0, "Modules Area End"},
59 struct ptdump_state ptdump
;
62 unsigned int current_prot
;
64 unsigned long wx_pages
;
65 unsigned long start_address
;
66 const struct addr_marker
*marker
;
69 #define pt_dump_seq_printf(m, fmt, args...) \
71 struct seq_file *__m = (m); \
74 seq_printf(__m, fmt, ##args); \
77 #define pt_dump_seq_puts(m, fmt) \
79 struct seq_file *__m = (m); \
82 seq_printf(__m, fmt); \
85 static void print_prot(struct seq_file
*m
, unsigned int pr
, int level
)
87 static const char * const level_name
[] =
88 { "ASCE", "PGD", "PUD", "PMD", "PTE" };
90 pt_dump_seq_printf(m
, "%s ", level_name
[level
]);
91 if (pr
& _PAGE_INVALID
) {
92 pt_dump_seq_printf(m
, "I\n");
95 pt_dump_seq_puts(m
, (pr
& _PAGE_PROTECT
) ? "RO " : "RW ");
96 pt_dump_seq_puts(m
, (pr
& _PAGE_NOEXEC
) ? "NX\n" : "X\n");
99 static void note_prot_wx(struct pg_state
*st
, unsigned long addr
)
101 #ifdef CONFIG_DEBUG_WX
104 if (st
->current_prot
& _PAGE_INVALID
)
106 if (st
->current_prot
& _PAGE_PROTECT
)
108 if (st
->current_prot
& _PAGE_NOEXEC
)
110 /* The first lowcore page is currently still W+X. */
111 if (addr
== PAGE_SIZE
)
113 WARN_ONCE(1, "s390/mm: Found insecure W+X mapping at address %pS\n",
114 (void *)st
->start_address
);
115 st
->wx_pages
+= (addr
- st
->start_address
) / PAGE_SIZE
;
116 #endif /* CONFIG_DEBUG_WX */
119 static void note_page(struct ptdump_state
*pt_st
, unsigned long addr
, int level
, u64 val
)
121 int width
= sizeof(unsigned long) * 2;
122 static const char units
[] = "KMGTPE";
123 const char *unit
= units
;
129 st
= container_of(pt_st
, struct pg_state
, ptdump
);
131 prot
= val
& (_PAGE_PROTECT
| _PAGE_NOEXEC
);
132 if (level
== 4 && (val
& _PAGE_INVALID
))
133 prot
= _PAGE_INVALID
;
134 /* For pmd_none() & friends val gets passed as zero. */
135 if (level
!= 4 && !val
)
136 prot
= _PAGE_INVALID
;
137 /* Final flush from generic code. */
140 if (st
->level
== -1) {
141 pt_dump_seq_printf(m
, "---[ %s ]---\n", st
->marker
->name
);
142 st
->start_address
= addr
;
143 st
->current_prot
= prot
;
145 } else if (prot
!= st
->current_prot
|| level
!= st
->level
||
146 addr
>= st
->marker
[1].start_address
) {
147 note_prot_wx(st
, addr
);
148 pt_dump_seq_printf(m
, "0x%0*lx-0x%0*lx ",
149 width
, st
->start_address
,
151 delta
= (addr
- st
->start_address
) >> 10;
152 while (!(delta
& 0x3ff) && unit
[1]) {
156 pt_dump_seq_printf(m
, "%9lu%c ", delta
, *unit
);
157 print_prot(m
, st
->current_prot
, st
->level
);
158 while (addr
>= st
->marker
[1].start_address
) {
160 pt_dump_seq_printf(m
, "---[ %s ]---\n", st
->marker
->name
);
162 st
->start_address
= addr
;
163 st
->current_prot
= prot
;
168 #ifdef CONFIG_DEBUG_WX
169 void ptdump_check_wx(void)
171 struct pg_state st
= {
173 .note_page
= note_page
,
174 .range
= (struct ptdump_range
[]) {
175 {.start
= 0, .end
= max_addr
},
176 {.start
= 0, .end
= 0},
185 .marker
= (struct addr_marker
[]) {
186 { .start_address
= 0, .name
= NULL
},
187 { .start_address
= -1, .name
= NULL
},
193 ptdump_walk_pgd(&st
.ptdump
, &init_mm
, NULL
);
195 pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n", st
.wx_pages
);
197 pr_info("Checked W+X mappings: passed, no unexpected W+X pages found\n");
199 #endif /* CONFIG_DEBUG_WX */
201 #ifdef CONFIG_PTDUMP_DEBUGFS
202 static int ptdump_show(struct seq_file
*m
, void *v
)
204 struct pg_state st
= {
206 .note_page
= note_page
,
207 .range
= (struct ptdump_range
[]) {
208 {.start
= 0, .end
= max_addr
},
209 {.start
= 0, .end
= 0},
218 .marker
= address_markers
,
222 mutex_lock(&cpa_mutex
);
223 ptdump_walk_pgd(&st
.ptdump
, &init_mm
, NULL
);
224 mutex_unlock(&cpa_mutex
);
228 DEFINE_SHOW_ATTRIBUTE(ptdump
);
229 #endif /* CONFIG_PTDUMP_DEBUGFS */
232 * Heapsort from lib/sort.c is not a stable sorting algorithm, do a simple
233 * insertion sort to preserve the original order of markers with the same
236 static void sort_address_markers(void)
238 struct addr_marker tmp
;
241 for (i
= 1; i
< ARRAY_SIZE(address_markers
) - 1; i
++) {
242 tmp
= address_markers
[i
];
243 for (j
= i
- 1; j
>= 0 && address_markers
[j
].start_address
> tmp
.start_address
; j
--)
244 address_markers
[j
+ 1] = address_markers
[j
];
245 address_markers
[j
+ 1] = tmp
;
249 static int pt_dump_init(void)
252 * Figure out the maximum virtual address being accessible with the
253 * kernel ASCE. We need this to keep the page table walker functions
254 * from accessing non-existent entries.
256 max_addr
= (S390_lowcore
.kernel_asce
& _REGION_ENTRY_TYPE_MASK
) >> 2;
257 max_addr
= 1UL << (max_addr
* 11 + 31);
258 address_markers
[IDENTITY_AFTER_END_NR
].start_address
= ident_map_size
;
259 address_markers
[MODULES_NR
].start_address
= MODULES_VADDR
;
260 address_markers
[MODULES_END_NR
].start_address
= MODULES_END
;
261 address_markers
[VMEMMAP_NR
].start_address
= (unsigned long) vmemmap
;
262 address_markers
[VMEMMAP_END_NR
].start_address
= (unsigned long)vmemmap
+ vmemmap_size
;
263 address_markers
[VMALLOC_NR
].start_address
= VMALLOC_START
;
264 address_markers
[VMALLOC_END_NR
].start_address
= VMALLOC_END
;
265 sort_address_markers();
266 #ifdef CONFIG_PTDUMP_DEBUGFS
267 debugfs_create_file("kernel_page_tables", 0400, NULL
, NULL
, &ptdump_fops
);
268 #endif /* CONFIG_PTDUMP_DEBUGFS */
271 device_initcall(pt_dump_init
);