]> Git Repo - linux.git/blob - arch/s390/mm/dump_pagetables.c
Merge tag 's390-6.12-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
[linux.git] / arch / s390 / mm / dump_pagetables.c
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>
6 #include <linux/sort.h>
7 #include <linux/mm.h>
8 #include <linux/kfence.h>
9 #include <linux/kasan.h>
10 #include <asm/kasan.h>
11 #include <asm/abs_lowcore.h>
12 #include <asm/nospec-branch.h>
13 #include <asm/sections.h>
14 #include <asm/maccess.h>
15
16 static unsigned long max_addr;
17
18 struct addr_marker {
19         int is_start;
20         unsigned long start_address;
21         unsigned long size;
22         const char *name;
23 };
24
25 static struct addr_marker *markers;
26 static unsigned int markers_cnt;
27
28 struct pg_state {
29         struct ptdump_state ptdump;
30         struct seq_file *seq;
31         int level;
32         unsigned int current_prot;
33         bool check_wx;
34         unsigned long wx_pages;
35         unsigned long start_address;
36         const struct addr_marker *marker;
37 };
38
39 #define pt_dump_seq_printf(m, fmt, args...)     \
40 ({                                              \
41         struct seq_file *__m = (m);             \
42                                                 \
43         if (__m)                                \
44                 seq_printf(__m, fmt, ##args);   \
45 })
46
47 #define pt_dump_seq_puts(m, fmt)                \
48 ({                                              \
49         struct seq_file *__m = (m);             \
50                                                 \
51         if (__m)                                \
52                 seq_printf(__m, fmt);           \
53 })
54
55 static void print_prot(struct seq_file *m, unsigned int pr, int level)
56 {
57         static const char * const level_name[] =
58                 { "ASCE", "PGD", "PUD", "PMD", "PTE" };
59
60         pt_dump_seq_printf(m, "%s ", level_name[level]);
61         if (pr & _PAGE_INVALID) {
62                 pt_dump_seq_printf(m, "I\n");
63                 return;
64         }
65         pt_dump_seq_puts(m, (pr & _PAGE_PROTECT) ? "RO " : "RW ");
66         pt_dump_seq_puts(m, (pr & _PAGE_NOEXEC) ? "NX\n" : "X\n");
67 }
68
69 static void note_prot_wx(struct pg_state *st, unsigned long addr)
70 {
71         if (!st->check_wx)
72                 return;
73         if (st->current_prot & _PAGE_INVALID)
74                 return;
75         if (st->current_prot & _PAGE_PROTECT)
76                 return;
77         if (st->current_prot & _PAGE_NOEXEC)
78                 return;
79         /*
80          * The first lowcore page is W+X if spectre mitigations are using
81          * trampolines or the BEAR enhancements facility is not installed,
82          * in which case we have two lpswe instructions in lowcore that need
83          * to be executable.
84          */
85         if (addr == PAGE_SIZE && (nospec_uses_trampoline() || !static_key_enabled(&cpu_has_bear)))
86                 return;
87         WARN_ONCE(IS_ENABLED(CONFIG_DEBUG_WX),
88                   "s390/mm: Found insecure W+X mapping at address %pS\n",
89                   (void *)st->start_address);
90         st->wx_pages += (addr - st->start_address) / PAGE_SIZE;
91 }
92
93 static void note_page_update_state(struct pg_state *st, unsigned long addr, unsigned int prot, int level)
94 {
95         struct seq_file *m = st->seq;
96
97         while (addr >= st->marker[1].start_address) {
98                 st->marker++;
99                 pt_dump_seq_printf(m, "---[ %s %s ]---\n", st->marker->name,
100                                    st->marker->is_start ? "Start" : "End");
101         }
102         st->start_address = addr;
103         st->current_prot = prot;
104         st->level = level;
105 }
106
107 static void note_page(struct ptdump_state *pt_st, unsigned long addr, int level, u64 val)
108 {
109         int width = sizeof(unsigned long) * 2;
110         static const char units[] = "KMGTPE";
111         const char *unit = units;
112         unsigned long delta;
113         struct pg_state *st;
114         struct seq_file *m;
115         unsigned int prot;
116
117         st = container_of(pt_st, struct pg_state, ptdump);
118         m = st->seq;
119         prot = val & (_PAGE_PROTECT | _PAGE_NOEXEC);
120         if (level == 4 && (val & _PAGE_INVALID))
121                 prot = _PAGE_INVALID;
122         /* For pmd_none() & friends val gets passed as zero. */
123         if (level != 4 && !val)
124                 prot = _PAGE_INVALID;
125         /* Final flush from generic code. */
126         if (level == -1)
127                 addr = max_addr;
128         if (st->level == -1) {
129                 pt_dump_seq_puts(m, "---[ Kernel Virtual Address Space ]---\n");
130                 note_page_update_state(st, addr, prot, level);
131         } else if (prot != st->current_prot || level != st->level ||
132                    addr >= st->marker[1].start_address) {
133                 note_prot_wx(st, addr);
134                 pt_dump_seq_printf(m, "0x%0*lx-0x%0*lx ",
135                                    width, st->start_address,
136                                    width, addr);
137                 delta = (addr - st->start_address) >> 10;
138                 while (!(delta & 0x3ff) && unit[1]) {
139                         delta >>= 10;
140                         unit++;
141                 }
142                 pt_dump_seq_printf(m, "%9lu%c ", delta, *unit);
143                 print_prot(m, st->current_prot, st->level);
144                 note_page_update_state(st, addr, prot, level);
145         }
146 }
147
148 bool ptdump_check_wx(void)
149 {
150         struct pg_state st = {
151                 .ptdump = {
152                         .note_page = note_page,
153                         .range = (struct ptdump_range[]) {
154                                 {.start = 0, .end = max_addr},
155                                 {.start = 0, .end = 0},
156                         }
157                 },
158                 .seq = NULL,
159                 .level = -1,
160                 .current_prot = 0,
161                 .check_wx = true,
162                 .wx_pages = 0,
163                 .start_address = 0,
164                 .marker = (struct addr_marker[]) {
165                         { .start_address =  0, .name = NULL},
166                         { .start_address = -1, .name = NULL},
167                 },
168         };
169
170         if (!MACHINE_HAS_NX)
171                 return true;
172         ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
173         if (st.wx_pages) {
174                 pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found\n", st.wx_pages);
175
176                 return false;
177         } else {
178                 pr_info("Checked W+X mappings: passed, no %sW+X pages found\n",
179                         (nospec_uses_trampoline() || !static_key_enabled(&cpu_has_bear)) ?
180                         "unexpected " : "");
181
182                 return true;
183         }
184 }
185
186 #ifdef CONFIG_PTDUMP_DEBUGFS
187 static int ptdump_show(struct seq_file *m, void *v)
188 {
189         struct pg_state st = {
190                 .ptdump = {
191                         .note_page = note_page,
192                         .range = (struct ptdump_range[]) {
193                                 {.start = 0, .end = max_addr},
194                                 {.start = 0, .end = 0},
195                         }
196                 },
197                 .seq = m,
198                 .level = -1,
199                 .current_prot = 0,
200                 .check_wx = false,
201                 .wx_pages = 0,
202                 .start_address = 0,
203                 .marker = markers,
204         };
205
206         get_online_mems();
207         mutex_lock(&cpa_mutex);
208         ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
209         mutex_unlock(&cpa_mutex);
210         put_online_mems();
211         return 0;
212 }
213 DEFINE_SHOW_ATTRIBUTE(ptdump);
214 #endif /* CONFIG_PTDUMP_DEBUGFS */
215
216 static int ptdump_cmp(const void *a, const void *b)
217 {
218         const struct addr_marker *ama = a;
219         const struct addr_marker *amb = b;
220
221         if (ama->start_address > amb->start_address)
222                 return 1;
223         if (ama->start_address < amb->start_address)
224                 return -1;
225         /*
226          * If the start addresses of two markers are identical sort markers in an
227          * order that considers areas contained within other areas correctly.
228          */
229         if (ama->is_start && amb->is_start) {
230                 if (ama->size > amb->size)
231                         return -1;
232                 if (ama->size < amb->size)
233                         return 1;
234                 return 0;
235         }
236         if (!ama->is_start && !amb->is_start) {
237                 if (ama->size > amb->size)
238                         return 1;
239                 if (ama->size < amb->size)
240                         return -1;
241                 return 0;
242         }
243         if (ama->is_start)
244                 return 1;
245         if (amb->is_start)
246                 return -1;
247         return 0;
248 }
249
250 static int add_marker(unsigned long start, unsigned long end, const char *name)
251 {
252         size_t oldsize, newsize;
253
254         oldsize = markers_cnt * sizeof(*markers);
255         newsize = oldsize + 2 * sizeof(*markers);
256         if (!oldsize)
257                 markers = kvmalloc(newsize, GFP_KERNEL);
258         else
259                 markers = kvrealloc(markers, newsize, GFP_KERNEL);
260         if (!markers)
261                 goto error;
262         markers[markers_cnt].is_start = 1;
263         markers[markers_cnt].start_address = start;
264         markers[markers_cnt].size = end - start;
265         markers[markers_cnt].name = name;
266         markers_cnt++;
267         markers[markers_cnt].is_start = 0;
268         markers[markers_cnt].start_address = end;
269         markers[markers_cnt].size = end - start;
270         markers[markers_cnt].name = name;
271         markers_cnt++;
272         return 0;
273 error:
274         markers_cnt = 0;
275         return -ENOMEM;
276 }
277
278 static int pt_dump_init(void)
279 {
280 #ifdef CONFIG_KFENCE
281         unsigned long kfence_start = (unsigned long)__kfence_pool;
282 #endif
283         unsigned long lowcore = (unsigned long)get_lowcore();
284         int rc;
285
286         /*
287          * Figure out the maximum virtual address being accessible with the
288          * kernel ASCE. We need this to keep the page table walker functions
289          * from accessing non-existent entries.
290          */
291         max_addr = (get_lowcore()->kernel_asce.val & _REGION_ENTRY_TYPE_MASK) >> 2;
292         max_addr = 1UL << (max_addr * 11 + 31);
293         /* start + end markers - must be added first */
294         rc = add_marker(0, -1UL, NULL);
295         rc |= add_marker((unsigned long)_stext, (unsigned long)_end, "Kernel Image");
296         rc |= add_marker(lowcore, lowcore + sizeof(struct lowcore), "Lowcore");
297         rc |= add_marker(__identity_base, __identity_base + ident_map_size, "Identity Mapping");
298         rc |= add_marker((unsigned long)__samode31, (unsigned long)__eamode31, "Amode31 Area");
299         rc |= add_marker(MODULES_VADDR, MODULES_END, "Modules Area");
300         rc |= add_marker(__abs_lowcore, __abs_lowcore + ABS_LOWCORE_MAP_SIZE, "Lowcore Area");
301         rc |= add_marker(__memcpy_real_area, __memcpy_real_area + MEMCPY_REAL_SIZE, "Real Memory Copy Area");
302         rc |= add_marker((unsigned long)vmemmap, (unsigned long)vmemmap + vmemmap_size, "vmemmap Area");
303         rc |= add_marker(VMALLOC_START, VMALLOC_END, "vmalloc Area");
304 #ifdef CONFIG_KFENCE
305         rc |= add_marker(kfence_start, kfence_start + KFENCE_POOL_SIZE, "KFence Pool");
306 #endif
307 #ifdef CONFIG_KMSAN
308         rc |= add_marker(KMSAN_VMALLOC_SHADOW_START, KMSAN_VMALLOC_SHADOW_END, "Kmsan vmalloc Shadow");
309         rc |= add_marker(KMSAN_VMALLOC_ORIGIN_START, KMSAN_VMALLOC_ORIGIN_END, "Kmsan vmalloc Origins");
310         rc |= add_marker(KMSAN_MODULES_SHADOW_START, KMSAN_MODULES_SHADOW_END, "Kmsan Modules Shadow");
311         rc |= add_marker(KMSAN_MODULES_ORIGIN_START, KMSAN_MODULES_ORIGIN_END, "Kmsan Modules Origins");
312 #endif
313 #ifdef CONFIG_KASAN
314         rc |= add_marker(KASAN_SHADOW_START, KASAN_SHADOW_END, "Kasan Shadow");
315 #endif
316         if (rc)
317                 goto error;
318         sort(&markers[1], markers_cnt - 1, sizeof(*markers), ptdump_cmp, NULL);
319 #ifdef CONFIG_PTDUMP_DEBUGFS
320         debugfs_create_file("kernel_page_tables", 0400, NULL, NULL, &ptdump_fops);
321 #endif /* CONFIG_PTDUMP_DEBUGFS */
322         return 0;
323 error:
324         kvfree(markers);
325         return -ENOMEM;
326 }
327 device_initcall(pt_dump_init);
This page took 0.051475 seconds and 4 git commands to generate.