Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
1da177e4 LT |
2 | /* |
3 | * fs/proc/kcore.c kernel ELF core dumper | |
4 | * | |
5 | * Modelled on fs/exec.c:aout_core_dump() | |
6 | * Jeremy Fitzhardinge <jeremy@sw.oz.au> | |
7 | * ELF version written by David Howells <David.Howells@nexor.co.uk> | |
8 | * Modified and incorporated into 2.3.x by Tigran Aivazian <tigran@veritas.com> | |
9 | * Support to dump vmalloc'd areas (ELF only), Tigran Aivazian <tigran@veritas.com> | |
10 | * Safe accesses to vmalloc/direct-mapped discontiguous areas, Kanoj Sarcar <kanoj@sgi.com> | |
11 | */ | |
12 | ||
23c85094 | 13 | #include <linux/crash_core.h> |
1da177e4 LT |
14 | #include <linux/mm.h> |
15 | #include <linux/proc_fs.h> | |
2f96b8c1 | 16 | #include <linux/kcore.h> |
1da177e4 | 17 | #include <linux/user.h> |
16f7e0fe | 18 | #include <linux/capability.h> |
1da177e4 LT |
19 | #include <linux/elf.h> |
20 | #include <linux/elfcore.h> | |
3c743a7f | 21 | #include <linux/notifier.h> |
1da177e4 LT |
22 | #include <linux/vmalloc.h> |
23 | #include <linux/highmem.h> | |
87ebdc00 | 24 | #include <linux/printk.h> |
57c8a661 | 25 | #include <linux/memblock.h> |
1da177e4 | 26 | #include <linux/init.h> |
5a0e3ad6 | 27 | #include <linux/slab.h> |
7c0f6ba6 | 28 | #include <linux/uaccess.h> |
1da177e4 | 29 | #include <asm/io.h> |
2ef43ec7 | 30 | #include <linux/list.h> |
3089aa1b | 31 | #include <linux/ioport.h> |
3089aa1b | 32 | #include <linux/memory.h> |
29930025 | 33 | #include <linux/sched/task.h> |
02e935bf | 34 | #include <linux/security.h> |
9492587c | 35 | #include <asm/sections.h> |
59d8053f | 36 | #include "internal.h" |
1da177e4 | 37 | |
36027604 | 38 | #define CORE_STR "CORE" |
1da177e4 | 39 | |
79885b22 EI |
40 | #ifndef ELF_CORE_EFLAGS |
41 | #define ELF_CORE_EFLAGS 0 | |
42 | #endif | |
43 | ||
97ce5d6d AD |
44 | static struct proc_dir_entry *proc_root_kcore; |
45 | ||
1da177e4 LT |
46 | |
47 | #ifndef kc_vaddr_to_offset | |
48 | #define kc_vaddr_to_offset(v) ((v) - PAGE_OFFSET) | |
49 | #endif | |
50 | #ifndef kc_offset_to_vaddr | |
51 | #define kc_offset_to_vaddr(o) ((o) + PAGE_OFFSET) | |
52 | #endif | |
53 | ||
2ef43ec7 | 54 | static LIST_HEAD(kclist_head); |
0b172f84 | 55 | static DECLARE_RWSEM(kclist_lock); |
3089aa1b | 56 | static int kcore_need_update = 1; |
1da177e4 | 57 | |
ffc8599a KS |
58 | /* |
59 | * Returns > 0 for RAM pages, 0 for non-RAM pages, < 0 on error | |
60 | * Same as oldmem_pfn_is_ram in vmcore | |
61 | */ | |
62 | static int (*mem_pfn_is_ram)(unsigned long pfn); | |
63 | ||
64 | int __init register_mem_pfn_is_ram(int (*fn)(unsigned long pfn)) | |
65 | { | |
66 | if (mem_pfn_is_ram) | |
67 | return -EBUSY; | |
68 | mem_pfn_is_ram = fn; | |
69 | return 0; | |
70 | } | |
71 | ||
72 | static int pfn_is_ram(unsigned long pfn) | |
73 | { | |
74 | if (mem_pfn_is_ram) | |
75 | return mem_pfn_is_ram(pfn); | |
76 | else | |
77 | return 1; | |
78 | } | |
79 | ||
a8dd9c4d OS |
80 | /* This doesn't grab kclist_lock, so it should only be used at init time. */ |
81 | void __init kclist_add(struct kcore_list *new, void *addr, size_t size, | |
82 | int type) | |
1da177e4 LT |
83 | { |
84 | new->addr = (unsigned long)addr; | |
85 | new->size = size; | |
c30bb2a2 | 86 | new->type = type; |
1da177e4 | 87 | |
2ef43ec7 | 88 | list_add_tail(&new->list, &kclist_head); |
1da177e4 LT |
89 | } |
90 | ||
37e949bd OS |
91 | static size_t get_kcore_size(int *nphdr, size_t *phdrs_len, size_t *notes_len, |
92 | size_t *data_offset) | |
1da177e4 LT |
93 | { |
94 | size_t try, size; | |
95 | struct kcore_list *m; | |
96 | ||
97 | *nphdr = 1; /* PT_NOTE */ | |
98 | size = 0; | |
99 | ||
2ef43ec7 | 100 | list_for_each_entry(m, &kclist_head, list) { |
1da177e4 LT |
101 | try = kc_vaddr_to_offset((size_t)m->addr + m->size); |
102 | if (try > size) | |
103 | size = try; | |
104 | *nphdr = *nphdr + 1; | |
105 | } | |
37e949bd OS |
106 | |
107 | *phdrs_len = *nphdr * sizeof(struct elf_phdr); | |
23c85094 OS |
108 | *notes_len = (4 * sizeof(struct elf_note) + |
109 | 3 * ALIGN(sizeof(CORE_STR), 4) + | |
110 | VMCOREINFO_NOTE_NAME_BYTES + | |
37e949bd OS |
111 | ALIGN(sizeof(struct elf_prstatus), 4) + |
112 | ALIGN(sizeof(struct elf_prpsinfo), 4) + | |
23c85094 OS |
113 | ALIGN(arch_task_struct_size, 4) + |
114 | ALIGN(vmcoreinfo_size, 4)); | |
37e949bd OS |
115 | *data_offset = PAGE_ALIGN(sizeof(struct elfhdr) + *phdrs_len + |
116 | *notes_len); | |
117 | return *data_offset + size; | |
1da177e4 LT |
118 | } |
119 | ||
3089aa1b KH |
120 | #ifdef CONFIG_HIGHMEM |
121 | /* | |
122 | * If no highmem, we can assume [0...max_low_pfn) continuous range of memory | |
123 | * because memory hole is not as big as !HIGHMEM case. | |
124 | * (HIGHMEM is special because part of memory is _invisible_ from the kernel.) | |
125 | */ | |
b66fb005 | 126 | static int kcore_ram_list(struct list_head *head) |
3089aa1b | 127 | { |
3089aa1b | 128 | struct kcore_list *ent; |
3089aa1b KH |
129 | |
130 | ent = kmalloc(sizeof(*ent), GFP_KERNEL); | |
131 | if (!ent) | |
132 | return -ENOMEM; | |
133 | ent->addr = (unsigned long)__va(0); | |
134 | ent->size = max_low_pfn << PAGE_SHIFT; | |
135 | ent->type = KCORE_RAM; | |
b66fb005 OS |
136 | list_add(&ent->list, head); |
137 | return 0; | |
3089aa1b KH |
138 | } |
139 | ||
140 | #else /* !CONFIG_HIGHMEM */ | |
141 | ||
26562c59 KH |
142 | #ifdef CONFIG_SPARSEMEM_VMEMMAP |
143 | /* calculate vmemmap's address from given system ram pfn and register it */ | |
b908243c DH |
144 | static int |
145 | get_sparsemem_vmemmap_info(struct kcore_list *ent, struct list_head *head) | |
26562c59 KH |
146 | { |
147 | unsigned long pfn = __pa(ent->addr) >> PAGE_SHIFT; | |
148 | unsigned long nr_pages = ent->size >> PAGE_SHIFT; | |
149 | unsigned long start, end; | |
150 | struct kcore_list *vmm, *tmp; | |
151 | ||
152 | ||
153 | start = ((unsigned long)pfn_to_page(pfn)) & PAGE_MASK; | |
154 | end = ((unsigned long)pfn_to_page(pfn + nr_pages)) - 1; | |
108a8a11 | 155 | end = PAGE_ALIGN(end); |
26562c59 KH |
156 | /* overlap check (because we have to align page */ |
157 | list_for_each_entry(tmp, head, list) { | |
158 | if (tmp->type != KCORE_VMEMMAP) | |
159 | continue; | |
160 | if (start < tmp->addr + tmp->size) | |
161 | if (end > tmp->addr) | |
162 | end = tmp->addr; | |
163 | } | |
164 | if (start < end) { | |
165 | vmm = kmalloc(sizeof(*vmm), GFP_KERNEL); | |
166 | if (!vmm) | |
167 | return 0; | |
168 | vmm->addr = start; | |
169 | vmm->size = end - start; | |
170 | vmm->type = KCORE_VMEMMAP; | |
171 | list_add_tail(&vmm->list, head); | |
172 | } | |
173 | return 1; | |
174 | ||
175 | } | |
176 | #else | |
b908243c DH |
177 | static int |
178 | get_sparsemem_vmemmap_info(struct kcore_list *ent, struct list_head *head) | |
26562c59 KH |
179 | { |
180 | return 1; | |
181 | } | |
182 | ||
183 | #endif | |
184 | ||
3089aa1b KH |
185 | static int |
186 | kclist_add_private(unsigned long pfn, unsigned long nr_pages, void *arg) | |
187 | { | |
188 | struct list_head *head = (struct list_head *)arg; | |
189 | struct kcore_list *ent; | |
3955333d LA |
190 | struct page *p; |
191 | ||
192 | if (!pfn_valid(pfn)) | |
193 | return 1; | |
194 | ||
195 | p = pfn_to_page(pfn); | |
196 | if (!memmap_valid_within(pfn, p, page_zone(p))) | |
197 | return 1; | |
3089aa1b KH |
198 | |
199 | ent = kmalloc(sizeof(*ent), GFP_KERNEL); | |
200 | if (!ent) | |
201 | return -ENOMEM; | |
3955333d | 202 | ent->addr = (unsigned long)page_to_virt(p); |
3089aa1b KH |
203 | ent->size = nr_pages << PAGE_SHIFT; |
204 | ||
3955333d | 205 | if (!virt_addr_valid(ent->addr)) |
3089aa1b KH |
206 | goto free_out; |
207 | ||
208 | /* cut not-mapped area. ....from ppc-32 code. */ | |
209 | if (ULONG_MAX - ent->addr < ent->size) | |
210 | ent->size = ULONG_MAX - ent->addr; | |
211 | ||
3955333d LA |
212 | /* |
213 | * We've already checked virt_addr_valid so we know this address | |
214 | * is a valid pointer, therefore we can check against it to determine | |
215 | * if we need to trim | |
216 | */ | |
217 | if (VMALLOC_START > ent->addr) { | |
3089aa1b KH |
218 | if (VMALLOC_START - ent->addr < ent->size) |
219 | ent->size = VMALLOC_START - ent->addr; | |
220 | } | |
221 | ||
222 | ent->type = KCORE_RAM; | |
223 | list_add_tail(&ent->list, head); | |
26562c59 KH |
224 | |
225 | if (!get_sparsemem_vmemmap_info(ent, head)) { | |
226 | list_del(&ent->list); | |
227 | goto free_out; | |
228 | } | |
229 | ||
3089aa1b KH |
230 | return 0; |
231 | free_out: | |
232 | kfree(ent); | |
233 | return 1; | |
234 | } | |
235 | ||
b66fb005 | 236 | static int kcore_ram_list(struct list_head *list) |
3089aa1b KH |
237 | { |
238 | int nid, ret; | |
239 | unsigned long end_pfn; | |
3089aa1b KH |
240 | |
241 | /* Not inialized....update now */ | |
242 | /* find out "max pfn" */ | |
243 | end_pfn = 0; | |
4ff1b2c2 | 244 | for_each_node_state(nid, N_MEMORY) { |
3089aa1b | 245 | unsigned long node_end; |
83285c72 | 246 | node_end = node_end_pfn(nid); |
3089aa1b KH |
247 | if (end_pfn < node_end) |
248 | end_pfn = node_end; | |
249 | } | |
250 | /* scan 0 to max_pfn */ | |
b66fb005 OS |
251 | ret = walk_system_ram_range(0, end_pfn, list, kclist_add_private); |
252 | if (ret) | |
3089aa1b | 253 | return -ENOMEM; |
b66fb005 OS |
254 | return 0; |
255 | } | |
256 | #endif /* CONFIG_HIGHMEM */ | |
257 | ||
258 | static int kcore_update_ram(void) | |
259 | { | |
260 | LIST_HEAD(list); | |
261 | LIST_HEAD(garbage); | |
262 | int nphdr; | |
37e949bd | 263 | size_t phdrs_len, notes_len, data_offset; |
b66fb005 OS |
264 | struct kcore_list *tmp, *pos; |
265 | int ret = 0; | |
266 | ||
267 | down_write(&kclist_lock); | |
268 | if (!xchg(&kcore_need_update, 0)) | |
269 | goto out; | |
270 | ||
271 | ret = kcore_ram_list(&list); | |
272 | if (ret) { | |
273 | /* Couldn't get the RAM list, try again next time. */ | |
274 | WRITE_ONCE(kcore_need_update, 1); | |
275 | list_splice_tail(&list, &garbage); | |
276 | goto out; | |
277 | } | |
278 | ||
279 | list_for_each_entry_safe(pos, tmp, &kclist_head, list) { | |
280 | if (pos->type == KCORE_RAM || pos->type == KCORE_VMEMMAP) | |
281 | list_move(&pos->list, &garbage); | |
282 | } | |
283 | list_splice_tail(&list, &kclist_head); | |
284 | ||
37e949bd OS |
285 | proc_root_kcore->size = get_kcore_size(&nphdr, &phdrs_len, ¬es_len, |
286 | &data_offset); | |
b66fb005 OS |
287 | |
288 | out: | |
289 | up_write(&kclist_lock); | |
290 | list_for_each_entry_safe(pos, tmp, &garbage, list) { | |
291 | list_del(&pos->list); | |
292 | kfree(pos); | |
3089aa1b | 293 | } |
3089aa1b KH |
294 | return ret; |
295 | } | |
1da177e4 | 296 | |
37e949bd OS |
297 | static void append_kcore_note(char *notes, size_t *i, const char *name, |
298 | unsigned int type, const void *desc, | |
299 | size_t descsz) | |
1da177e4 | 300 | { |
37e949bd OS |
301 | struct elf_note *note = (struct elf_note *)¬es[*i]; |
302 | ||
303 | note->n_namesz = strlen(name) + 1; | |
304 | note->n_descsz = descsz; | |
305 | note->n_type = type; | |
306 | *i += sizeof(*note); | |
307 | memcpy(¬es[*i], name, note->n_namesz); | |
308 | *i = ALIGN(*i + note->n_namesz, 4); | |
309 | memcpy(¬es[*i], desc, descsz); | |
310 | *i = ALIGN(*i + descsz, 4); | |
311 | } | |
1da177e4 | 312 | |
1da177e4 LT |
313 | static ssize_t |
314 | read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) | |
315 | { | |
f5beeb18 | 316 | char *buf = file->private_data; |
37e949bd OS |
317 | size_t phdrs_offset, notes_offset, data_offset; |
318 | size_t phdrs_len, notes_len; | |
319 | struct kcore_list *m; | |
320 | size_t tsz; | |
1da177e4 LT |
321 | int nphdr; |
322 | unsigned long start; | |
3673fb08 OS |
323 | size_t orig_buflen = buflen; |
324 | int ret = 0; | |
1da177e4 | 325 | |
0b172f84 | 326 | down_read(&kclist_lock); |
678ad5d8 | 327 | |
37e949bd OS |
328 | get_kcore_size(&nphdr, &phdrs_len, ¬es_len, &data_offset); |
329 | phdrs_offset = sizeof(struct elfhdr); | |
330 | notes_offset = phdrs_offset + phdrs_len; | |
331 | ||
332 | /* ELF file header. */ | |
333 | if (buflen && *fpos < sizeof(struct elfhdr)) { | |
334 | struct elfhdr ehdr = { | |
335 | .e_ident = { | |
336 | [EI_MAG0] = ELFMAG0, | |
337 | [EI_MAG1] = ELFMAG1, | |
338 | [EI_MAG2] = ELFMAG2, | |
339 | [EI_MAG3] = ELFMAG3, | |
340 | [EI_CLASS] = ELF_CLASS, | |
341 | [EI_DATA] = ELF_DATA, | |
342 | [EI_VERSION] = EV_CURRENT, | |
343 | [EI_OSABI] = ELF_OSABI, | |
344 | }, | |
345 | .e_type = ET_CORE, | |
346 | .e_machine = ELF_ARCH, | |
347 | .e_version = EV_CURRENT, | |
348 | .e_phoff = sizeof(struct elfhdr), | |
349 | .e_flags = ELF_CORE_EFLAGS, | |
350 | .e_ehsize = sizeof(struct elfhdr), | |
351 | .e_phentsize = sizeof(struct elf_phdr), | |
352 | .e_phnum = nphdr, | |
353 | }; | |
354 | ||
355 | tsz = min_t(size_t, buflen, sizeof(struct elfhdr) - *fpos); | |
356 | if (copy_to_user(buffer, (char *)&ehdr + *fpos, tsz)) { | |
357 | ret = -EFAULT; | |
358 | goto out; | |
359 | } | |
1da177e4 | 360 | |
37e949bd OS |
361 | buffer += tsz; |
362 | buflen -= tsz; | |
363 | *fpos += tsz; | |
364 | } | |
1da177e4 | 365 | |
37e949bd OS |
366 | /* ELF program headers. */ |
367 | if (buflen && *fpos < phdrs_offset + phdrs_len) { | |
368 | struct elf_phdr *phdrs, *phdr; | |
1da177e4 | 369 | |
37e949bd OS |
370 | phdrs = kzalloc(phdrs_len, GFP_KERNEL); |
371 | if (!phdrs) { | |
3673fb08 OS |
372 | ret = -ENOMEM; |
373 | goto out; | |
1da177e4 | 374 | } |
37e949bd OS |
375 | |
376 | phdrs[0].p_type = PT_NOTE; | |
377 | phdrs[0].p_offset = notes_offset; | |
378 | phdrs[0].p_filesz = notes_len; | |
379 | ||
380 | phdr = &phdrs[1]; | |
381 | list_for_each_entry(m, &kclist_head, list) { | |
382 | phdr->p_type = PT_LOAD; | |
383 | phdr->p_flags = PF_R | PF_W | PF_X; | |
384 | phdr->p_offset = kc_vaddr_to_offset(m->addr) + data_offset; | |
d207ea8e LT |
385 | if (m->type == KCORE_REMAP) |
386 | phdr->p_vaddr = (size_t)m->vaddr; | |
387 | else | |
388 | phdr->p_vaddr = (size_t)m->addr; | |
389 | if (m->type == KCORE_RAM || m->type == KCORE_REMAP) | |
37e949bd OS |
390 | phdr->p_paddr = __pa(m->addr); |
391 | else if (m->type == KCORE_TEXT) | |
392 | phdr->p_paddr = __pa_symbol(m->addr); | |
393 | else | |
394 | phdr->p_paddr = (elf_addr_t)-1; | |
395 | phdr->p_filesz = phdr->p_memsz = m->size; | |
396 | phdr->p_align = PAGE_SIZE; | |
397 | phdr++; | |
398 | } | |
399 | ||
400 | tsz = min_t(size_t, buflen, phdrs_offset + phdrs_len - *fpos); | |
401 | if (copy_to_user(buffer, (char *)phdrs + *fpos - phdrs_offset, | |
402 | tsz)) { | |
403 | kfree(phdrs); | |
3673fb08 OS |
404 | ret = -EFAULT; |
405 | goto out; | |
1da177e4 | 406 | } |
37e949bd OS |
407 | kfree(phdrs); |
408 | ||
409 | buffer += tsz; | |
1da177e4 LT |
410 | buflen -= tsz; |
411 | *fpos += tsz; | |
37e949bd OS |
412 | } |
413 | ||
414 | /* ELF note segment. */ | |
415 | if (buflen && *fpos < notes_offset + notes_len) { | |
416 | struct elf_prstatus prstatus = {}; | |
417 | struct elf_prpsinfo prpsinfo = { | |
418 | .pr_sname = 'R', | |
419 | .pr_fname = "vmlinux", | |
420 | }; | |
421 | char *notes; | |
422 | size_t i = 0; | |
423 | ||
424 | strlcpy(prpsinfo.pr_psargs, saved_command_line, | |
425 | sizeof(prpsinfo.pr_psargs)); | |
426 | ||
427 | notes = kzalloc(notes_len, GFP_KERNEL); | |
428 | if (!notes) { | |
429 | ret = -ENOMEM; | |
430 | goto out; | |
431 | } | |
432 | ||
433 | append_kcore_note(notes, &i, CORE_STR, NT_PRSTATUS, &prstatus, | |
434 | sizeof(prstatus)); | |
435 | append_kcore_note(notes, &i, CORE_STR, NT_PRPSINFO, &prpsinfo, | |
436 | sizeof(prpsinfo)); | |
437 | append_kcore_note(notes, &i, CORE_STR, NT_TASKSTRUCT, current, | |
438 | arch_task_struct_size); | |
23c85094 OS |
439 | /* |
440 | * vmcoreinfo_size is mostly constant after init time, but it | |
441 | * can be changed by crash_save_vmcoreinfo(). Racing here with a | |
442 | * panic on another CPU before the machine goes down is insanely | |
443 | * unlikely, but it's better to not leave potential buffer | |
444 | * overflows lying around, regardless. | |
445 | */ | |
446 | append_kcore_note(notes, &i, VMCOREINFO_NOTE_NAME, 0, | |
447 | vmcoreinfo_data, | |
448 | min(vmcoreinfo_size, notes_len - i)); | |
1da177e4 | 449 | |
37e949bd OS |
450 | tsz = min_t(size_t, buflen, notes_offset + notes_len - *fpos); |
451 | if (copy_to_user(buffer, notes + *fpos - notes_offset, tsz)) { | |
452 | kfree(notes); | |
453 | ret = -EFAULT; | |
3673fb08 | 454 | goto out; |
37e949bd OS |
455 | } |
456 | kfree(notes); | |
457 | ||
458 | buffer += tsz; | |
459 | buflen -= tsz; | |
460 | *fpos += tsz; | |
3673fb08 | 461 | } |
1da177e4 LT |
462 | |
463 | /* | |
464 | * Check to see if our file offset matches with any of | |
465 | * the addresses in the elf_phdr on our list. | |
466 | */ | |
37e949bd | 467 | start = kc_offset_to_vaddr(*fpos - data_offset); |
1da177e4 LT |
468 | if ((tsz = (PAGE_SIZE - (start & ~PAGE_MASK))) > buflen) |
469 | tsz = buflen; | |
1da177e4 | 470 | |
bf991c22 | 471 | m = NULL; |
37e949bd | 472 | while (buflen) { |
bf991c22 OS |
473 | /* |
474 | * If this is the first iteration or the address is not within | |
475 | * the previous entry, search for a matching entry. | |
476 | */ | |
477 | if (!m || start < m->addr || start >= m->addr + m->size) { | |
478 | list_for_each_entry(m, &kclist_head, list) { | |
479 | if (start >= m->addr && | |
480 | start < m->addr + m->size) | |
481 | break; | |
482 | } | |
1da177e4 | 483 | } |
1da177e4 | 484 | |
4fd2c20d | 485 | if (&m->list == &kclist_head) { |
3673fb08 OS |
486 | if (clear_user(buffer, tsz)) { |
487 | ret = -EFAULT; | |
488 | goto out; | |
489 | } | |
a1b3d2f2 | 490 | m = NULL; /* skip the list anchor */ |
ffc8599a KS |
491 | } else if (!pfn_is_ram(__pa(start) >> PAGE_SHIFT)) { |
492 | if (clear_user(buffer, tsz)) { | |
493 | ret = -EFAULT; | |
494 | goto out; | |
495 | } | |
737326aa | 496 | } else if (m->type == KCORE_VMALLOC) { |
f5beeb18 | 497 | vread(buf, (char *)start, tsz); |
73d7c33e | 498 | /* we have to zero-fill user buffer even if no read */ |
3673fb08 OS |
499 | if (copy_to_user(buffer, buf, tsz)) { |
500 | ret = -EFAULT; | |
501 | goto out; | |
502 | } | |
595dd46e JZ |
503 | } else if (m->type == KCORE_USER) { |
504 | /* User page is handled prior to normal kernel page: */ | |
3673fb08 OS |
505 | if (copy_to_user(buffer, (char *)start, tsz)) { |
506 | ret = -EFAULT; | |
507 | goto out; | |
508 | } | |
1da177e4 LT |
509 | } else { |
510 | if (kern_addr_valid(start)) { | |
df04abfd JO |
511 | /* |
512 | * Using bounce buffer to bypass the | |
513 | * hardened user copy kernel text checks. | |
514 | */ | |
d0290bc2 | 515 | if (probe_kernel_read(buf, (void *) start, tsz)) { |
3673fb08 OS |
516 | if (clear_user(buffer, tsz)) { |
517 | ret = -EFAULT; | |
518 | goto out; | |
519 | } | |
d0290bc2 | 520 | } else { |
3673fb08 OS |
521 | if (copy_to_user(buffer, buf, tsz)) { |
522 | ret = -EFAULT; | |
523 | goto out; | |
524 | } | |
1da177e4 LT |
525 | } |
526 | } else { | |
3673fb08 OS |
527 | if (clear_user(buffer, tsz)) { |
528 | ret = -EFAULT; | |
529 | goto out; | |
530 | } | |
1da177e4 LT |
531 | } |
532 | } | |
533 | buflen -= tsz; | |
534 | *fpos += tsz; | |
535 | buffer += tsz; | |
1da177e4 LT |
536 | start += tsz; |
537 | tsz = (buflen > PAGE_SIZE ? PAGE_SIZE : buflen); | |
538 | } | |
539 | ||
3673fb08 OS |
540 | out: |
541 | up_read(&kclist_lock); | |
542 | if (ret) | |
543 | return ret; | |
544 | return orig_buflen - buflen; | |
1da177e4 | 545 | } |
97ce5d6d | 546 | |
3089aa1b KH |
547 | static int open_kcore(struct inode *inode, struct file *filp) |
548 | { | |
02e935bf DH |
549 | int ret = security_locked_down(LOCKDOWN_KCORE); |
550 | ||
3089aa1b KH |
551 | if (!capable(CAP_SYS_RAWIO)) |
552 | return -EPERM; | |
f5beeb18 | 553 | |
b602614a MG |
554 | if (ret) |
555 | return ret; | |
556 | ||
f5beeb18 JO |
557 | filp->private_data = kmalloc(PAGE_SIZE, GFP_KERNEL); |
558 | if (!filp->private_data) | |
559 | return -ENOMEM; | |
560 | ||
3089aa1b KH |
561 | if (kcore_need_update) |
562 | kcore_update_ram(); | |
0d4c36a9 | 563 | if (i_size_read(inode) != proc_root_kcore->size) { |
5955102c | 564 | inode_lock(inode); |
0d4c36a9 | 565 | i_size_write(inode, proc_root_kcore->size); |
5955102c | 566 | inode_unlock(inode); |
0d4c36a9 | 567 | } |
3089aa1b KH |
568 | return 0; |
569 | } | |
570 | ||
f5beeb18 JO |
571 | static int release_kcore(struct inode *inode, struct file *file) |
572 | { | |
573 | kfree(file->private_data); | |
574 | return 0; | |
575 | } | |
3089aa1b | 576 | |
97a32539 AD |
577 | static const struct proc_ops kcore_proc_ops = { |
578 | .proc_read = read_kcore, | |
579 | .proc_open = open_kcore, | |
580 | .proc_release = release_kcore, | |
581 | .proc_lseek = default_llseek, | |
3089aa1b KH |
582 | }; |
583 | ||
3089aa1b KH |
584 | /* just remember that we have to update kcore */ |
585 | static int __meminit kcore_callback(struct notifier_block *self, | |
586 | unsigned long action, void *arg) | |
587 | { | |
588 | switch (action) { | |
589 | case MEM_ONLINE: | |
590 | case MEM_OFFLINE: | |
3089aa1b | 591 | kcore_need_update = 1; |
bf531831 | 592 | break; |
3089aa1b KH |
593 | } |
594 | return NOTIFY_OK; | |
595 | } | |
3089aa1b | 596 | |
3c743a7f AM |
597 | static struct notifier_block kcore_callback_nb __meminitdata = { |
598 | .notifier_call = kcore_callback, | |
599 | .priority = 0, | |
600 | }; | |
3089aa1b | 601 | |
a0614da8 KH |
602 | static struct kcore_list kcore_vmalloc; |
603 | ||
9492587c KH |
604 | #ifdef CONFIG_ARCH_PROC_KCORE_TEXT |
605 | static struct kcore_list kcore_text; | |
606 | /* | |
607 | * If defined, special segment is used for mapping kernel text instead of | |
608 | * direct-map area. We need to create special TEXT section. | |
609 | */ | |
610 | static void __init proc_kcore_text_init(void) | |
611 | { | |
36e15263 | 612 | kclist_add(&kcore_text, _text, _end - _text, KCORE_TEXT); |
9492587c KH |
613 | } |
614 | #else | |
615 | static void __init proc_kcore_text_init(void) | |
616 | { | |
617 | } | |
618 | #endif | |
619 | ||
81ac3ad9 KH |
620 | #if defined(CONFIG_MODULES) && defined(MODULES_VADDR) |
621 | /* | |
622 | * MODULES_VADDR has no intersection with VMALLOC_ADDR. | |
623 | */ | |
eebf3648 | 624 | static struct kcore_list kcore_modules; |
81ac3ad9 KH |
625 | static void __init add_modules_range(void) |
626 | { | |
bf3e2692 BH |
627 | if (MODULES_VADDR != VMALLOC_START && MODULES_END != VMALLOC_END) { |
628 | kclist_add(&kcore_modules, (void *)MODULES_VADDR, | |
81ac3ad9 | 629 | MODULES_END - MODULES_VADDR, KCORE_VMALLOC); |
bf3e2692 | 630 | } |
81ac3ad9 KH |
631 | } |
632 | #else | |
633 | static void __init add_modules_range(void) | |
634 | { | |
635 | } | |
636 | #endif | |
637 | ||
97ce5d6d AD |
638 | static int __init proc_kcore_init(void) |
639 | { | |
97a32539 | 640 | proc_root_kcore = proc_create("kcore", S_IRUSR, NULL, &kcore_proc_ops); |
90396f96 | 641 | if (!proc_root_kcore) { |
87ebdc00 | 642 | pr_err("couldn't create /proc/kcore\n"); |
90396f96 KH |
643 | return 0; /* Always returns 0. */ |
644 | } | |
3089aa1b | 645 | /* Store text area if it's special */ |
9492587c | 646 | proc_kcore_text_init(); |
3089aa1b | 647 | /* Store vmalloc area */ |
a0614da8 KH |
648 | kclist_add(&kcore_vmalloc, (void *)VMALLOC_START, |
649 | VMALLOC_END - VMALLOC_START, KCORE_VMALLOC); | |
81ac3ad9 | 650 | add_modules_range(); |
3089aa1b KH |
651 | /* Store direct-map area from physical memory map */ |
652 | kcore_update_ram(); | |
3c743a7f | 653 | register_hotmemory_notifier(&kcore_callback_nb); |
3089aa1b | 654 | |
97ce5d6d AD |
655 | return 0; |
656 | } | |
abaf3787 | 657 | fs_initcall(proc_kcore_init); |