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