[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-changelog] [linux-2.6.18-xen] privcmd: avoid deadlock due to copy_(to|from)_user with mmap_sem write lock held.
# HG changeset patch # User Ian Campbell <ian.campbell@xxxxxxxxxx> # Date 1214337767 -3600 # Node ID 043dc7488c1105670a78cd45efa459fc4defa52d # Parent 5201a184f5131c76f8263aa70c031c00dd094067 privcmd: avoid deadlock due to copy_(to|from)_user with mmap_sem write lock held. Accessing user memory with the write lock held is illegal, if the pages are non-present then we will deadlock against the lock in do_page_fault. Avoid this for IOCTL_PRIVCMD_MMAP and MMAPBATCH by copying the entire input data structure into the kernel before taking the lock. Signed-off-by: Ian Campbell <ian.campbell@xxxxxxxxxx> --- drivers/xen/privcmd/privcmd.c | 162 ++++++++++++++++++++++++++++++------------ 1 files changed, 117 insertions(+), 45 deletions(-) diff -r 5201a184f513 -r 043dc7488c11 drivers/xen/privcmd/privcmd.c --- a/drivers/xen/privcmd/privcmd.c Fri Jun 20 17:43:16 2008 +0100 +++ b/drivers/xen/privcmd/privcmd.c Tue Jun 24 21:02:47 2008 +0100 @@ -93,13 +93,16 @@ static long privcmd_ioctl(struct file *f break; case IOCTL_PRIVCMD_MMAP: { +#define MMAP_NR_PER_PAGE (int)((PAGE_SIZE-sizeof(struct list_head))/sizeof(privcmd_mmap_entry_t)) privcmd_mmap_t mmapcmd; - privcmd_mmap_entry_t msg; + privcmd_mmap_entry_t *msg; privcmd_mmap_entry_t __user *p; struct mm_struct *mm = current->mm; struct vm_area_struct *vma; unsigned long va; int i, rc; + LIST_HEAD(pagelist); + struct list_head *l,*l2; if (!is_initial_xendomain()) return -EPERM; @@ -108,63 +111,92 @@ static long privcmd_ioctl(struct file *f return -EFAULT; p = mmapcmd.entry; - if (copy_from_user(&msg, p, sizeof(msg))) - return -EFAULT; + for (i = 0; i < mmapcmd.num;) { + int nr = min(mmapcmd.num - i, MMAP_NR_PER_PAGE); + + rc = -ENOMEM; + l = (struct list_head *) __get_free_page(GFP_KERNEL); + if (l == NULL) + goto mmap_out; + + INIT_LIST_HEAD(l); + list_add_tail(l, &pagelist); + msg = (privcmd_mmap_entry_t*)(l + 1); + + rc = -EFAULT; + if (copy_from_user(msg, p, nr*sizeof(*msg))) + goto mmap_out; + i += nr; + p += nr; + } + + l = pagelist.next; + msg = (privcmd_mmap_entry_t*)(l + 1); down_write(&mm->mmap_sem); - vma = find_vma(mm, msg.va); + vma = find_vma(mm, msg->va); rc = -EINVAL; - if (!vma || (msg.va != vma->vm_start) || + if (!vma || (msg->va != vma->vm_start) || !privcmd_enforce_singleshot_mapping(vma)) goto mmap_out; va = vma->vm_start; - for (i = 0; i < mmapcmd.num; i++) { - rc = -EFAULT; - if (copy_from_user(&msg, p, sizeof(msg))) - goto mmap_out; - - /* Do not allow range to wrap the address space. */ - rc = -EINVAL; - if ((msg.npages > (LONG_MAX >> PAGE_SHIFT)) || - ((unsigned long)(msg.npages << PAGE_SHIFT) >= -va)) - goto mmap_out; - - /* Range chunks must be contiguous in va space. */ - if ((msg.va != va) || - ((msg.va+(msg.npages<<PAGE_SHIFT)) > vma->vm_end)) - goto mmap_out; - - if ((rc = direct_remap_pfn_range( - vma, - msg.va & PAGE_MASK, - msg.mfn, - msg.npages << PAGE_SHIFT, - vma->vm_page_prot, - mmapcmd.dom)) < 0) - goto mmap_out; - - p++; - va += msg.npages << PAGE_SHIFT; + i = 0; + list_for_each(l, &pagelist) { + int nr = i + min(mmapcmd.num - i, MMAP_NR_PER_PAGE); + + msg = (privcmd_mmap_entry_t*)(l + 1); + while (i<nr) { + + /* Do not allow range to wrap the address space. */ + rc = -EINVAL; + if ((msg->npages > (LONG_MAX >> PAGE_SHIFT)) || + ((unsigned long)(msg->npages << PAGE_SHIFT) >= -va)) + goto mmap_out; + + /* Range chunks must be contiguous in va space. */ + if ((msg->va != va) || + ((msg->va+(msg->npages<<PAGE_SHIFT)) > vma->vm_end)) + goto mmap_out; + + if ((rc = direct_remap_pfn_range( + vma, + msg->va & PAGE_MASK, + msg->mfn, + msg->npages << PAGE_SHIFT, + vma->vm_page_prot, + mmapcmd.dom)) < 0) + goto mmap_out; + + va += msg->npages << PAGE_SHIFT; + msg++; + i++; + } } rc = 0; mmap_out: up_write(&mm->mmap_sem); + list_for_each_safe(l,l2,&pagelist) + free_page((unsigned long)l); ret = rc; } +#undef MMAP_NR_PER_PAGE break; case IOCTL_PRIVCMD_MMAPBATCH: { +#define MMAPBATCH_NR_PER_PAGE (unsigned long)((PAGE_SIZE-sizeof(struct list_head))/sizeof(unsigned long)) privcmd_mmapbatch_t m; struct mm_struct *mm = current->mm; struct vm_area_struct *vma; xen_pfn_t __user *p; - unsigned long addr, mfn, nr_pages; + unsigned long addr, *mfn, nr_pages; int i; + LIST_HEAD(pagelist); + struct list_head *l, *l2; if (!is_initial_xendomain()) return -EPERM; @@ -176,34 +208,74 @@ static long privcmd_ioctl(struct file *f if ((m.num <= 0) || (nr_pages > (LONG_MAX >> PAGE_SHIFT))) return -EINVAL; + p = m.arr; + for (i=0; i<nr_pages; ) { + int nr = min(nr_pages - i, MMAPBATCH_NR_PER_PAGE); + + ret = -ENOMEM; + l = (struct list_head *)__get_free_page(GFP_KERNEL); + if (l == NULL) + goto mmapbatch_out; + + INIT_LIST_HEAD(l); + list_add_tail(l, &pagelist); + + mfn = (unsigned long*)(l + 1); + ret = -EFAULT; + if (copy_from_user(mfn, p, nr*sizeof(*mfn))) + goto mmapbatch_out; + + i += nr; p+= nr; + } + down_write(&mm->mmap_sem); vma = find_vma(mm, m.addr); + ret = -EINVAL; if (!vma || (m.addr != vma->vm_start) || ((m.addr + (nr_pages << PAGE_SHIFT)) != vma->vm_end) || !privcmd_enforce_singleshot_mapping(vma)) { up_write(&mm->mmap_sem); - return -EINVAL; + goto mmapbatch_out; } p = m.arr; addr = m.addr; - for (i = 0; i < nr_pages; i++, addr += PAGE_SIZE, p++) { - if (get_user(mfn, p)) { - up_write(&mm->mmap_sem); - return -EFAULT; + i = 0; + ret = 0; + list_for_each(l, &pagelist) { + int nr = i + min(nr_pages - i, MMAPBATCH_NR_PER_PAGE); + mfn = (unsigned long *)(l + 1); + + while (i<nr) { + if(direct_remap_pfn_range(vma, addr & PAGE_MASK, + *mfn, PAGE_SIZE, + vma->vm_page_prot, m.dom) < 0) { + *mfn |= 0xf0000000U; + ret++; + } + mfn++; i++; addr += PAGE_SIZE; } - - ret = direct_remap_pfn_range(vma, addr & PAGE_MASK, - mfn, PAGE_SIZE, - vma->vm_page_prot, m.dom); - if (ret < 0) - put_user(0xF0000000 | mfn, p); } up_write(&mm->mmap_sem); - ret = 0; + if (ret > 0) { + p = m.arr; + i = 0; + ret = 0; + list_for_each(l, &pagelist) { + int nr = min(nr_pages - i, MMAPBATCH_NR_PER_PAGE); + mfn = (unsigned long *)(l + 1); + if (copy_to_user(p, mfn, nr*sizeof(*mfn))) + ret = -EFAULT; + i += nr; p += nr; + } + } + mmapbatch_out: + list_for_each_safe(l,l2,&pagelist) + free_page((unsigned long)l); +#undef MMAPBATCH_NR_PER_PAGE } break; _______________________________________________ Xen-changelog mailing list Xen-changelog@xxxxxxxxxxxxxxxxxxx http://lists.xensource.com/xen-changelog
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |