mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	proc: pagemap: Hold mmap_sem during page walk
In initial design, walk_page_range() was designed just for walking page table and it didn't require mmap_sem. Now, find_vma() etc.. are used in walk_page_range() and we need mmap_sem around it. This patch adds mmap_sem around walk_page_range(). Because /proc/<pid>/pagemap's callback routine use put_user(), we have to get rid of it to do sane fix. Changelog: 2010/Apr/2 - fixed start_vaddr and end overflow Changelog: 2010/Apr/1 - fixed start_vaddr calculation - removed unnecessary cast. - removed unnecessary change in smaps. - use GFP_TEMPORARY instead of GFP_KERNEL Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Cc: Matt Mackall <mpm@selenic.com> Cc: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> Cc: San Mehat <san@google.com> Cc: Brian Swetland <swetland@google.com> Cc: Dave Hansen <haveblue@us.ibm.com> Cc: Andrew Morton <akpm@linux-foundation.org> [ Fixed kmalloc failure return code as per Matt ] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
		
							parent
							
								
									5e11611a5d
								
							
						
					
					
						commit
						d82ef020cf
					
				
					 1 changed files with 37 additions and 48 deletions
				
			
		| 
						 | 
				
			
			@ -406,6 +406,7 @@ static int show_smap(struct seq_file *m, void *v)
 | 
			
		|||
 | 
			
		||||
	memset(&mss, 0, sizeof mss);
 | 
			
		||||
	mss.vma = vma;
 | 
			
		||||
	/* mmap_sem is held in m_start */
 | 
			
		||||
	if (vma->vm_mm && !is_vm_hugetlb_page(vma))
 | 
			
		||||
		walk_page_range(vma->vm_start, vma->vm_end, &smaps_walk);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -552,7 +553,8 @@ const struct file_operations proc_clear_refs_operations = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
struct pagemapread {
 | 
			
		||||
	u64 __user *out, *end;
 | 
			
		||||
	int pos, len;
 | 
			
		||||
	u64 *buffer;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define PM_ENTRY_BYTES      sizeof(u64)
 | 
			
		||||
| 
						 | 
				
			
			@ -575,10 +577,8 @@ struct pagemapread {
 | 
			
		|||
static int add_to_pagemap(unsigned long addr, u64 pfn,
 | 
			
		||||
			  struct pagemapread *pm)
 | 
			
		||||
{
 | 
			
		||||
	if (put_user(pfn, pm->out))
 | 
			
		||||
		return -EFAULT;
 | 
			
		||||
	pm->out++;
 | 
			
		||||
	if (pm->out >= pm->end)
 | 
			
		||||
	pm->buffer[pm->pos++] = pfn;
 | 
			
		||||
	if (pm->pos >= pm->len)
 | 
			
		||||
		return PM_END_OF_BUFFER;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -720,21 +720,20 @@ static int pagemap_hugetlb_range(pte_t *pte, unsigned long addr,
 | 
			
		|||
 * determine which areas of memory are actually mapped and llseek to
 | 
			
		||||
 * skip over unmapped regions.
 | 
			
		||||
 */
 | 
			
		||||
#define PAGEMAP_WALK_SIZE	(PMD_SIZE)
 | 
			
		||||
static ssize_t pagemap_read(struct file *file, char __user *buf,
 | 
			
		||||
			    size_t count, loff_t *ppos)
 | 
			
		||||
{
 | 
			
		||||
	struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
 | 
			
		||||
	struct page **pages, *page;
 | 
			
		||||
	unsigned long uaddr, uend;
 | 
			
		||||
	struct mm_struct *mm;
 | 
			
		||||
	struct pagemapread pm;
 | 
			
		||||
	int pagecount;
 | 
			
		||||
	int ret = -ESRCH;
 | 
			
		||||
	struct mm_walk pagemap_walk = {};
 | 
			
		||||
	unsigned long src;
 | 
			
		||||
	unsigned long svpfn;
 | 
			
		||||
	unsigned long start_vaddr;
 | 
			
		||||
	unsigned long end_vaddr;
 | 
			
		||||
	int copied = 0;
 | 
			
		||||
 | 
			
		||||
	if (!task)
 | 
			
		||||
		goto out;
 | 
			
		||||
| 
						 | 
				
			
			@ -757,35 +756,12 @@ static ssize_t pagemap_read(struct file *file, char __user *buf,
 | 
			
		|||
	if (!mm)
 | 
			
		||||
		goto out_task;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	uaddr = (unsigned long)buf & PAGE_MASK;
 | 
			
		||||
	uend = (unsigned long)(buf + count);
 | 
			
		||||
	pagecount = (PAGE_ALIGN(uend) - uaddr) / PAGE_SIZE;
 | 
			
		||||
	ret = 0;
 | 
			
		||||
	if (pagecount == 0)
 | 
			
		||||
		goto out_mm;
 | 
			
		||||
	pages = kcalloc(pagecount, sizeof(struct page *), GFP_KERNEL);
 | 
			
		||||
	pm.len = PM_ENTRY_BYTES * (PAGEMAP_WALK_SIZE >> PAGE_SHIFT);
 | 
			
		||||
	pm.buffer = kmalloc(pm.len, GFP_TEMPORARY);
 | 
			
		||||
	ret = -ENOMEM;
 | 
			
		||||
	if (!pages)
 | 
			
		||||
	if (!pm.buffer)
 | 
			
		||||
		goto out_mm;
 | 
			
		||||
 | 
			
		||||
	down_read(¤t->mm->mmap_sem);
 | 
			
		||||
	ret = get_user_pages(current, current->mm, uaddr, pagecount,
 | 
			
		||||
			     1, 0, pages, NULL);
 | 
			
		||||
	up_read(¤t->mm->mmap_sem);
 | 
			
		||||
 | 
			
		||||
	if (ret < 0)
 | 
			
		||||
		goto out_free;
 | 
			
		||||
 | 
			
		||||
	if (ret != pagecount) {
 | 
			
		||||
		pagecount = ret;
 | 
			
		||||
		ret = -EFAULT;
 | 
			
		||||
		goto out_pages;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pm.out = (u64 __user *)buf;
 | 
			
		||||
	pm.end = (u64 __user *)(buf + count);
 | 
			
		||||
 | 
			
		||||
	pagemap_walk.pmd_entry = pagemap_pte_range;
 | 
			
		||||
	pagemap_walk.pte_hole = pagemap_pte_hole;
 | 
			
		||||
	pagemap_walk.hugetlb_entry = pagemap_hugetlb_range;
 | 
			
		||||
| 
						 | 
				
			
			@ -807,23 +783,36 @@ static ssize_t pagemap_read(struct file *file, char __user *buf,
 | 
			
		|||
	 * user buffer is tracked in "pm", and the walk
 | 
			
		||||
	 * will stop when we hit the end of the buffer.
 | 
			
		||||
	 */
 | 
			
		||||
	ret = walk_page_range(start_vaddr, end_vaddr, &pagemap_walk);
 | 
			
		||||
	if (ret == PM_END_OF_BUFFER)
 | 
			
		||||
	ret = 0;
 | 
			
		||||
	/* don't need mmap_sem for these, but this looks cleaner */
 | 
			
		||||
	*ppos += (char __user *)pm.out - buf;
 | 
			
		||||
	if (!ret)
 | 
			
		||||
		ret = (char __user *)pm.out - buf;
 | 
			
		||||
	while (count && (start_vaddr < end_vaddr)) {
 | 
			
		||||
		int len;
 | 
			
		||||
		unsigned long end;
 | 
			
		||||
 | 
			
		||||
out_pages:
 | 
			
		||||
	for (; pagecount; pagecount--) {
 | 
			
		||||
		page = pages[pagecount-1];
 | 
			
		||||
		if (!PageReserved(page))
 | 
			
		||||
			SetPageDirty(page);
 | 
			
		||||
		page_cache_release(page);
 | 
			
		||||
		pm.pos = 0;
 | 
			
		||||
		end = start_vaddr + PAGEMAP_WALK_SIZE;
 | 
			
		||||
		/* overflow ? */
 | 
			
		||||
		if (end < start_vaddr || end > end_vaddr)
 | 
			
		||||
			end = end_vaddr;
 | 
			
		||||
		down_read(&mm->mmap_sem);
 | 
			
		||||
		ret = walk_page_range(start_vaddr, end, &pagemap_walk);
 | 
			
		||||
		up_read(&mm->mmap_sem);
 | 
			
		||||
		start_vaddr = end;
 | 
			
		||||
 | 
			
		||||
		len = min(count, PM_ENTRY_BYTES * pm.pos);
 | 
			
		||||
		if (copy_to_user(buf, pm.buffer, len) < 0) {
 | 
			
		||||
			ret = -EFAULT;
 | 
			
		||||
			goto out_free;
 | 
			
		||||
		}
 | 
			
		||||
		copied += len;
 | 
			
		||||
		buf += len;
 | 
			
		||||
		count -= len;
 | 
			
		||||
	}
 | 
			
		||||
	*ppos += copied;
 | 
			
		||||
	if (!ret || ret == PM_END_OF_BUFFER)
 | 
			
		||||
		ret = copied;
 | 
			
		||||
 | 
			
		||||
out_free:
 | 
			
		||||
	kfree(pages);
 | 
			
		||||
	kfree(pm.buffer);
 | 
			
		||||
out_mm:
 | 
			
		||||
	mmput(mm);
 | 
			
		||||
out_task:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue