[PATCH] NOMMU: Permit ptrace to ignore non-PROT_WRITE VMAs in NOMMU mode
[powerpc.git] / mm / nommu.c
index 029fada..829fc90 100644 (file)
@@ -122,26 +122,50 @@ unsigned int kobjsize(const void *objp)
 }
 
 /*
- * The nommu dodgy version :-)
+ * get a list of pages in an address range belonging to the specified process
+ * and indicate the VMA that covers each page
+ * - this is potentially dodgy as we may end incrementing the page count of a
+ *   slab page or a secondary page from a compound page
+ * - don't permit access to VMAs that don't support it, such as I/O mappings
  */
 int get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
        unsigned long start, int len, int write, int force,
        struct page **pages, struct vm_area_struct **vmas)
 {
+       struct vm_area_struct *vma;
+       unsigned long vm_flags;
        int i;
-       static struct vm_area_struct dummy_vma;
+
+       /* calculate required read or write permissions.
+        * - if 'force' is set, we only require the "MAY" flags.
+        */
+       vm_flags  = write ? (VM_WRITE | VM_MAYWRITE) : (VM_READ | VM_MAYREAD);
+       vm_flags &= force ? (VM_MAYREAD | VM_MAYWRITE) : (VM_READ | VM_WRITE);
 
        for (i = 0; i < len; i++) {
+               vma = find_vma(mm, start);
+               if (!vma)
+                       goto finish_or_fault;
+
+               /* protect what we can, including chardevs */
+               if (vma->vm_flags & (VM_IO | VM_PFNMAP) ||
+                   !(vm_flags & vma->vm_flags))
+                       goto finish_or_fault;
+
                if (pages) {
                        pages[i] = virt_to_page(start);
                        if (pages[i])
                                page_cache_get(pages[i]);
                }
                if (vmas)
-                       vmas[i] = &dummy_vma;
+                       vmas[i] = vma;
                start += PAGE_SIZE;
        }
-       return(i);
+
+       return i;
+
+finish_or_fault:
+       return i ? : -EFAULT;
 }
 
 EXPORT_SYMBOL(get_user_pages);
@@ -1039,6 +1063,7 @@ unsigned long do_mremap(unsigned long addr,
 
 /*
  * Look up the first VMA which satisfies  addr < vm_end,  NULL if none
+ * - should be called with mm->mmap_sem at least readlocked
  */
 struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
 {
@@ -1070,6 +1095,7 @@ int remap_pfn_range(struct vm_area_struct *vma, unsigned long from,
        vma->vm_start = vma->vm_pgoff << PAGE_SHIFT;
        return 0;
 }
+EXPORT_SYMBOL(remap_pfn_range);
 
 void swap_unplug_io_fn(struct backing_dev_info *bdi, struct page *page)
 {
@@ -1090,6 +1116,7 @@ void unmap_mapping_range(struct address_space *mapping,
                         int even_cows)
 {
 }
+EXPORT_SYMBOL(unmap_mapping_range);
 
 /*
  * Check that a process has enough memory to allocate a new virtual
@@ -1122,7 +1149,7 @@ int __vm_enough_memory(long pages, int cap_sys_admin)
        if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) {
                unsigned long n;
 
-               free = get_page_cache_size();
+               free = global_page_state(NR_FILE_PAGES);
                free += nr_swap_pages;
 
                /*
@@ -1131,7 +1158,7 @@ int __vm_enough_memory(long pages, int cap_sys_admin)
                 * which are reclaimable, under pressure.  The dentry
                 * cache and most inode caches should fall into this
                 */
-               free += atomic_read(&slab_reclaim_pages);
+               free += global_page_state(NR_SLAB_RECLAIMABLE);
 
                /*
                 * Leave the last 3% for root
@@ -1204,3 +1231,44 @@ struct page *filemap_nopage(struct vm_area_struct *area,
        BUG();
        return NULL;
 }
+
+/*
+ * Access another process' address space.
+ * - source/target buffer must be kernel space
+ */
+int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write)
+{
+       struct vm_area_struct *vma;
+       struct mm_struct *mm;
+
+       if (addr + len < addr)
+               return 0;
+
+       mm = get_task_mm(tsk);
+       if (!mm)
+               return 0;
+
+       down_read(&mm->mmap_sem);
+
+       /* the access must start within one of the target process's mappings */
+       vma = find_vma(mm, addr);
+       if (vma) {
+               /* don't overrun this mapping */
+               if (addr + len >= vma->vm_end)
+                       len = vma->vm_end - addr;
+
+               /* only read or write mappings where it is permitted */
+               if (write && vma->vm_flags & VM_MAYWRITE)
+                       len -= copy_to_user((void *) addr, buf, len);
+               else if (!write && vma->vm_flags & VM_MAYREAD)
+                       len -= copy_from_user(buf, (void *) addr, len);
+               else
+                       len = 0;
+       } else {
+               len = 0;
+       }
+
+       up_read(&mm->mmap_sem);
+       mmput(mm);
+       return len;
+}