mm/core, x86/mm/pkeys: Differentiate instruction fetches
[linux-2.6-block.git] / mm / gup.c
index de24ef4cd1af84c592ec0cfd641e11f983eacf08..7f1c4fb77cfa5e9c6ca4fd4c04c83e01a9b273b1 100644 (file)
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -15,6 +15,7 @@
 #include <linux/rwsem.h>
 #include <linux/hugetlb.h>
 
+#include <asm/mmu_context.h>
 #include <asm/pgtable.h>
 #include <asm/tlbflush.h>
 
@@ -364,6 +365,8 @@ static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma,
                return -ENOENT;
        if (*flags & FOLL_WRITE)
                fault_flags |= FAULT_FLAG_WRITE;
+       if (*flags & FOLL_REMOTE)
+               fault_flags |= FAULT_FLAG_REMOTE;
        if (nonblocking)
                fault_flags |= FAULT_FLAG_ALLOW_RETRY;
        if (*flags & FOLL_NOWAIT)
@@ -414,11 +417,13 @@ static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma,
 static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)
 {
        vm_flags_t vm_flags = vma->vm_flags;
+       int write = (gup_flags & FOLL_WRITE);
+       int foreign = (gup_flags & FOLL_REMOTE);
 
        if (vm_flags & (VM_IO | VM_PFNMAP))
                return -EFAULT;
 
-       if (gup_flags & FOLL_WRITE) {
+       if (write) {
                if (!(vm_flags & VM_WRITE)) {
                        if (!(gup_flags & FOLL_FORCE))
                                return -EFAULT;
@@ -444,6 +449,12 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags)
                if (!(vm_flags & VM_MAYREAD))
                        return -EFAULT;
        }
+       /*
+        * gups are always data accesses, not instruction
+        * fetches, so execute=false here
+        */
+       if (!arch_vma_access_permitted(vma, write, false, foreign))
+               return -EFAULT;
        return 0;
 }
 
@@ -610,6 +621,28 @@ next_page:
 }
 EXPORT_SYMBOL(__get_user_pages);
 
+bool vma_permits_fault(struct vm_area_struct *vma, unsigned int fault_flags)
+{
+       bool write   = !!(fault_flags & FAULT_FLAG_WRITE);
+       bool foreign = !!(fault_flags & FAULT_FLAG_REMOTE);
+       vm_flags_t vm_flags = write ? VM_WRITE : VM_READ;
+
+       if (!(vm_flags & vma->vm_flags))
+               return false;
+
+       /*
+        * The architecture might have a hardware protection
+        * mechanism other than read/write that can deny access.
+        *
+        * gup always represents data access, not instruction
+        * fetches, so execute=false here:
+        */
+       if (!arch_vma_access_permitted(vma, write, false, foreign))
+               return false;
+
+       return true;
+}
+
 /*
  * fixup_user_fault() - manually resolve a user page fault
  * @tsk:       the task_struct to use for page fault accounting, or
@@ -645,7 +678,6 @@ int fixup_user_fault(struct task_struct *tsk, struct mm_struct *mm,
                     bool *unlocked)
 {
        struct vm_area_struct *vma;
-       vm_flags_t vm_flags;
        int ret, major = 0;
 
        if (unlocked)
@@ -656,8 +688,7 @@ retry:
        if (!vma || address < vma->vm_start)
                return -EFAULT;
 
-       vm_flags = (fault_flags & FAULT_FLAG_WRITE) ? VM_WRITE : VM_READ;
-       if (!(vm_flags & vma->vm_flags))
+       if (!vma_permits_fault(vma, fault_flags))
                return -EFAULT;
 
        ret = handle_mm_fault(mm, vma, address, fault_flags);
@@ -1162,6 +1193,9 @@ static int gup_pte_range(pmd_t pmd, unsigned long addr, unsigned long end,
                        pte_protnone(pte) || (write && !pte_write(pte)))
                        goto pte_unmap;
 
+               if (!arch_pte_access_permitted(pte, write))
+                       goto pte_unmap;
+
                VM_BUG_ON(!pfn_valid(pte_pfn(pte)));
                page = pte_page(pte);
                head = compound_head(page);