1585 lines · 40.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* *	fs/proc/vmcore.c Interface for accessing the crash * 				 dump from the system's previous life. * 	Heavily borrowed from fs/proc/kcore.c *	Created by: Hariprasad Nellitheertha (hari@in.ibm.com) *	Copyright (C) IBM Corporation, 2004. All rights reserved * */ #include <linux/mm.h>#include <linux/kcore.h>#include <linux/user.h>#include <linux/elf.h>#include <linux/elfcore.h>#include <linux/export.h>#include <linux/slab.h>#include <linux/highmem.h>#include <linux/printk.h>#include <linux/memblock.h>#include <linux/init.h>#include <linux/crash_dump.h>#include <linux/list.h>#include <linux/moduleparam.h>#include <linux/mutex.h>#include <linux/vmalloc.h>#include <linux/pagemap.h>#include <linux/uaccess.h>#include <linux/mem_encrypt.h>#include <asm/io.h>#include "internal.h" /* List representing chunks of contiguous memory areas and their offsets in * vmcore file. */static LIST_HEAD(vmcore_list); /* Stores the pointer to the buffer containing kernel elf core headers. */static char *elfcorebuf;static size_t elfcorebuf_sz;static size_t elfcorebuf_sz_orig; static char *elfnotes_buf;static size_t elfnotes_sz;/* Size of all notes minus the device dump notes */static size_t elfnotes_orig_sz; /* Total size of vmcore file. */static u64 vmcore_size; static struct proc_dir_entry *proc_vmcore; #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP/* Device Dump list and mutex to synchronize access to list */static LIST_HEAD(vmcoredd_list);static DEFINE_MUTEX(vmcoredd_mutex); static bool vmcoredd_disabled;core_param(novmcoredd, vmcoredd_disabled, bool, 0);#endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ /* Device Dump Size */static size_t vmcoredd_orig_sz; /* * Returns > 0 for RAM pages, 0 for non-RAM pages, < 0 on error * The called function has to take care of module refcounting. */static int (*oldmem_pfn_is_ram)(unsigned long pfn); int register_oldmem_pfn_is_ram(int (*fn)(unsigned long pfn)){	if (oldmem_pfn_is_ram)		return -EBUSY;	oldmem_pfn_is_ram = fn;	return 0;}EXPORT_SYMBOL_GPL(register_oldmem_pfn_is_ram); void unregister_oldmem_pfn_is_ram(void){	oldmem_pfn_is_ram = NULL;	wmb();}EXPORT_SYMBOL_GPL(unregister_oldmem_pfn_is_ram); static int pfn_is_ram(unsigned long pfn){	int (*fn)(unsigned long pfn);	/* pfn is ram unless fn() checks pagetype */	int ret = 1; 	/*	 * Ask hypervisor if the pfn is really ram.	 * A ballooned page contains no data and reading from such a page	 * will cause high load in the hypervisor.	 */	fn = oldmem_pfn_is_ram;	if (fn)		ret = fn(pfn); 	return ret;} /* Reads a page from the oldmem device from given offset. */ssize_t read_from_oldmem(char *buf, size_t count,			 u64 *ppos, int userbuf,			 bool encrypted){	unsigned long pfn, offset;	size_t nr_bytes;	ssize_t read = 0, tmp; 	if (!count)		return 0; 	offset = (unsigned long)(*ppos % PAGE_SIZE);	pfn = (unsigned long)(*ppos / PAGE_SIZE); 	do {		if (count > (PAGE_SIZE - offset))			nr_bytes = PAGE_SIZE - offset;		else			nr_bytes = count; 		/* If pfn is not ram, return zeros for sparse dump files */		if (pfn_is_ram(pfn) == 0)			memset(buf, 0, nr_bytes);		else {			if (encrypted)				tmp = copy_oldmem_page_encrypted(pfn, buf,								 nr_bytes,								 offset,								 userbuf);			else				tmp = copy_oldmem_page(pfn, buf, nr_bytes,						       offset, userbuf); 			if (tmp < 0)				return tmp;		}		*ppos += nr_bytes;		count -= nr_bytes;		buf += nr_bytes;		read += nr_bytes;		++pfn;		offset = 0;	} while (count); 	return read;} /* * Architectures may override this function to allocate ELF header in 2nd kernel */int __weak elfcorehdr_alloc(unsigned long long *addr, unsigned long long *size){	return 0;} /* * Architectures may override this function to free header */void __weak elfcorehdr_free(unsigned long long addr){} /* * Architectures may override this function to read from ELF header */ssize_t __weak elfcorehdr_read(char *buf, size_t count, u64 *ppos){	return read_from_oldmem(buf, count, ppos, 0, false);} /* * Architectures may override this function to read from notes sections */ssize_t __weak elfcorehdr_read_notes(char *buf, size_t count, u64 *ppos){	return read_from_oldmem(buf, count, ppos, 0, mem_encrypt_active());} /* * Architectures may override this function to map oldmem */int __weak remap_oldmem_pfn_range(struct vm_area_struct *vma,				  unsigned long from, unsigned long pfn,				  unsigned long size, pgprot_t prot){	prot = pgprot_encrypted(prot);	return remap_pfn_range(vma, from, pfn, size, prot);} /* * Architectures which support memory encryption override this. */ssize_t __weakcopy_oldmem_page_encrypted(unsigned long pfn, char *buf, size_t csize,			   unsigned long offset, int userbuf){	return copy_oldmem_page(pfn, buf, csize, offset, userbuf);} /* * Copy to either kernel or user space */static int copy_to(void *target, void *src, size_t size, int userbuf){	if (userbuf) {		if (copy_to_user((char __user *) target, src, size))			return -EFAULT;	} else {		memcpy(target, src, size);	}	return 0;} #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMPstatic int vmcoredd_copy_dumps(void *dst, u64 start, size_t size, int userbuf){	struct vmcoredd_node *dump;	u64 offset = 0;	int ret = 0;	size_t tsz;	char *buf; 	mutex_lock(&vmcoredd_mutex);	list_for_each_entry(dump, &vmcoredd_list, list) {		if (start < offset + dump->size) {			tsz = min(offset + (u64)dump->size - start, (u64)size);			buf = dump->buf + start - offset;			if (copy_to(dst, buf, tsz, userbuf)) {				ret = -EFAULT;				goto out_unlock;			} 			size -= tsz;			start += tsz;			dst += tsz; 			/* Leave now if buffer filled already */			if (!size)				goto out_unlock;		}		offset += dump->size;	} out_unlock:	mutex_unlock(&vmcoredd_mutex);	return ret;} #ifdef CONFIG_MMUstatic int vmcoredd_mmap_dumps(struct vm_area_struct *vma, unsigned long dst,			       u64 start, size_t size){	struct vmcoredd_node *dump;	u64 offset = 0;	int ret = 0;	size_t tsz;	char *buf; 	mutex_lock(&vmcoredd_mutex);	list_for_each_entry(dump, &vmcoredd_list, list) {		if (start < offset + dump->size) {			tsz = min(offset + (u64)dump->size - start, (u64)size);			buf = dump->buf + start - offset;			if (remap_vmalloc_range_partial(vma, dst, buf, 0,							tsz)) {				ret = -EFAULT;				goto out_unlock;			} 			size -= tsz;			start += tsz;			dst += tsz; 			/* Leave now if buffer filled already */			if (!size)				goto out_unlock;		}		offset += dump->size;	} out_unlock:	mutex_unlock(&vmcoredd_mutex);	return ret;}#endif /* CONFIG_MMU */#endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ /* Read from the ELF header and then the crash dump. On error, negative value is * returned otherwise number of bytes read are returned. */static ssize_t __read_vmcore(char *buffer, size_t buflen, loff_t *fpos,			     int userbuf){	ssize_t acc = 0, tmp;	size_t tsz;	u64 start;	struct vmcore *m = NULL; 	if (buflen == 0 || *fpos >= vmcore_size)		return 0; 	/* trim buflen to not go beyond EOF */	if (buflen > vmcore_size - *fpos)		buflen = vmcore_size - *fpos; 	/* Read ELF core header */	if (*fpos < elfcorebuf_sz) {		tsz = min(elfcorebuf_sz - (size_t)*fpos, buflen);		if (copy_to(buffer, elfcorebuf + *fpos, tsz, userbuf))			return -EFAULT;		buflen -= tsz;		*fpos += tsz;		buffer += tsz;		acc += tsz; 		/* leave now if filled buffer already */		if (buflen == 0)			return acc;	} 	/* Read Elf note segment */	if (*fpos < elfcorebuf_sz + elfnotes_sz) {		void *kaddr; 		/* We add device dumps before other elf notes because the		 * other elf notes may not fill the elf notes buffer		 * completely and we will end up with zero-filled data		 * between the elf notes and the device dumps. Tools will		 * then try to decode this zero-filled data as valid notes		 * and we don't want that. Hence, adding device dumps before		 * the other elf notes ensure that zero-filled data can be		 * avoided.		 */#ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP		/* Read device dumps */		if (*fpos < elfcorebuf_sz + vmcoredd_orig_sz) {			tsz = min(elfcorebuf_sz + vmcoredd_orig_sz -				  (size_t)*fpos, buflen);			start = *fpos - elfcorebuf_sz;			if (vmcoredd_copy_dumps(buffer, start, tsz, userbuf))				return -EFAULT; 			buflen -= tsz;			*fpos += tsz;			buffer += tsz;			acc += tsz; 			/* leave now if filled buffer already */			if (!buflen)				return acc;		}#endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ 		/* Read remaining elf notes */		tsz = min(elfcorebuf_sz + elfnotes_sz - (size_t)*fpos, buflen);		kaddr = elfnotes_buf + *fpos - elfcorebuf_sz - vmcoredd_orig_sz;		if (copy_to(buffer, kaddr, tsz, userbuf))			return -EFAULT; 		buflen -= tsz;		*fpos += tsz;		buffer += tsz;		acc += tsz; 		/* leave now if filled buffer already */		if (buflen == 0)			return acc;	} 	list_for_each_entry(m, &vmcore_list, list) {		if (*fpos < m->offset + m->size) {			tsz = (size_t)min_t(unsigned long long,					    m->offset + m->size - *fpos,					    buflen);			start = m->paddr + *fpos - m->offset;			tmp = read_from_oldmem(buffer, tsz, &start,					       userbuf, mem_encrypt_active());			if (tmp < 0)				return tmp;			buflen -= tsz;			*fpos += tsz;			buffer += tsz;			acc += tsz; 			/* leave now if filled buffer already */			if (buflen == 0)				return acc;		}	} 	return acc;} static ssize_t read_vmcore(struct file *file, char __user *buffer,			   size_t buflen, loff_t *fpos){	return __read_vmcore((__force char *) buffer, buflen, fpos, 1);} /* * The vmcore fault handler uses the page cache and fills data using the * standard __vmcore_read() function. * * On s390 the fault handler is used for memory regions that can't be mapped * directly with remap_pfn_range(). */static vm_fault_t mmap_vmcore_fault(struct vm_fault *vmf){#ifdef CONFIG_S390	struct address_space *mapping = vmf->vma->vm_file->f_mapping;	pgoff_t index = vmf->pgoff;	struct page *page;	loff_t offset;	char *buf;	int rc; 	page = find_or_create_page(mapping, index, GFP_KERNEL);	if (!page)		return VM_FAULT_OOM;	if (!PageUptodate(page)) {		offset = (loff_t) index << PAGE_SHIFT;		buf = __va((page_to_pfn(page) << PAGE_SHIFT));		rc = __read_vmcore(buf, PAGE_SIZE, &offset, 0);		if (rc < 0) {			unlock_page(page);			put_page(page);			return vmf_error(rc);		}		SetPageUptodate(page);	}	unlock_page(page);	vmf->page = page;	return 0;#else	return VM_FAULT_SIGBUS;#endif} static const struct vm_operations_struct vmcore_mmap_ops = {	.fault = mmap_vmcore_fault,}; /** * vmcore_alloc_buf - allocate buffer in vmalloc memory * @sizez: size of buffer * * If CONFIG_MMU is defined, use vmalloc_user() to allow users to mmap * the buffer to user-space by means of remap_vmalloc_range(). * * If CONFIG_MMU is not defined, use vzalloc() since mmap_vmcore() is * disabled and there's no need to allow users to mmap the buffer. */static inline char *vmcore_alloc_buf(size_t size){#ifdef CONFIG_MMU	return vmalloc_user(size);#else	return vzalloc(size);#endif} /* * Disable mmap_vmcore() if CONFIG_MMU is not defined. MMU is * essential for mmap_vmcore() in order to map physically * non-contiguous objects (ELF header, ELF note segment and memory * regions in the 1st kernel pointed to by PT_LOAD entries) into * virtually contiguous user-space in ELF layout. */#ifdef CONFIG_MMU/* * remap_oldmem_pfn_checked - do remap_oldmem_pfn_range replacing all pages * reported as not being ram with the zero page. * * @vma: vm_area_struct describing requested mapping * @from: start remapping from * @pfn: page frame number to start remapping to * @size: remapping size * @prot: protection bits * * Returns zero on success, -EAGAIN on failure. */static int remap_oldmem_pfn_checked(struct vm_area_struct *vma,				    unsigned long from, unsigned long pfn,				    unsigned long size, pgprot_t prot){	unsigned long map_size;	unsigned long pos_start, pos_end, pos;	unsigned long zeropage_pfn = my_zero_pfn(0);	size_t len = 0; 	pos_start = pfn;	pos_end = pfn + (size >> PAGE_SHIFT); 	for (pos = pos_start; pos < pos_end; ++pos) {		if (!pfn_is_ram(pos)) {			/*

Lines 1–500 of 1585.