net/mlx4_en: Resolve dividing by zero in 32-bit system
[cascardo/linux.git] / mm / mprotect.c
index dd3f40a..bcdbe62 100644 (file)
 #include <linux/mmu_notifier.h>
 #include <linux/migrate.h>
 #include <linux/perf_event.h>
+#include <linux/pkeys.h>
 #include <linux/ksm.h>
 #include <linux/pkeys.h>
 #include <asm/uaccess.h>
 #include <asm/pgtable.h>
 #include <asm/cacheflush.h>
+#include <asm/mmu_context.h>
 #include <asm/tlbflush.h>
 
 #include "internal.h"
@@ -304,6 +306,7 @@ mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
                           vma->vm_userfaultfd_ctx);
        if (*pprev) {
                vma = *pprev;
+               VM_WARN_ON((vma->vm_flags ^ newflags) & ~VM_SOFTDIRTY);
                goto success;
        }
 
@@ -327,7 +330,7 @@ success:
         * held in write mode.
         */
        vma->vm_flags = newflags;
-       dirty_accountable = vma_wants_writenotify(vma);
+       dirty_accountable = vma_wants_writenotify(vma, vma->vm_page_prot);
        vma_set_page_prot(vma);
 
        change_protection(vma, start, end, vma->vm_page_prot,
@@ -364,12 +367,6 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
        const int grows = prot & (PROT_GROWSDOWN|PROT_GROWSUP);
        const bool rier = (current->personality & READ_IMPLIES_EXEC) &&
                                (prot & PROT_READ);
-       /*
-        * A temporary safety check since we are not validating
-        * the pkey before we introduce the allocation code.
-        */
-       if (pkey != -1)
-               return -EINVAL;
 
        prot &= ~(PROT_GROWSDOWN|PROT_GROWSUP);
        if (grows == (PROT_GROWSDOWN|PROT_GROWSUP)) /* can't be both */
@@ -391,6 +388,14 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
        if (down_write_killable(&current->mm->mmap_sem))
                return -EINTR;
 
+       /*
+        * If userspace did not allocate the pkey, do not let
+        * them use it here.
+        */
+       error = -EINVAL;
+       if ((pkey != -1) && !mm_pkey_is_allocated(current->mm, pkey))
+               goto out;
+
        vma = find_vma(current->mm, start);
        error = -ENOMEM;
        if (!vma)
@@ -417,6 +422,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
                prev = vma;
 
        for (nstart = start ; ; ) {
+               unsigned long mask_off_old_flags;
                unsigned long newflags;
                int new_vma_pkey;
 
@@ -426,9 +432,17 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
                if (rier && (vma->vm_flags & VM_MAYEXEC))
                        prot |= PROT_EXEC;
 
+               /*
+                * Each mprotect() call explicitly passes r/w/x permissions.
+                * If a permission is not passed to mprotect(), it must be
+                * cleared from the VMA.
+                */
+               mask_off_old_flags = VM_READ | VM_WRITE | VM_EXEC |
+                                       ARCH_VM_PKEY_FLAGS;
+
                new_vma_pkey = arch_override_mprotect_pkey(vma, prot, pkey);
                newflags = calc_vm_prot_bits(prot, new_vma_pkey);
-               newflags |= (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_EXEC));
+               newflags |= (vma->vm_flags & ~mask_off_old_flags);
 
                /* newflags >> 4 shift VM_MAY% in place of VM_% */
                if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
@@ -476,3 +490,48 @@ SYSCALL_DEFINE4(pkey_mprotect, unsigned long, start, size_t, len,
 {
        return do_mprotect_pkey(start, len, prot, pkey);
 }
+
+SYSCALL_DEFINE2(pkey_alloc, unsigned long, flags, unsigned long, init_val)
+{
+       int pkey;
+       int ret;
+
+       /* No flags supported yet. */
+       if (flags)
+               return -EINVAL;
+       /* check for unsupported init values */
+       if (init_val & ~PKEY_ACCESS_MASK)
+               return -EINVAL;
+
+       down_write(&current->mm->mmap_sem);
+       pkey = mm_pkey_alloc(current->mm);
+
+       ret = -ENOSPC;
+       if (pkey == -1)
+               goto out;
+
+       ret = arch_set_user_pkey_access(current, pkey, init_val);
+       if (ret) {
+               mm_pkey_free(current->mm, pkey);
+               goto out;
+       }
+       ret = pkey;
+out:
+       up_write(&current->mm->mmap_sem);
+       return ret;
+}
+
+SYSCALL_DEFINE1(pkey_free, int, pkey)
+{
+       int ret;
+
+       down_write(&current->mm->mmap_sem);
+       ret = mm_pkey_free(current->mm, pkey);
+       up_write(&current->mm->mmap_sem);
+
+       /*
+        * We could provie warnings or errors if any VMA still
+        * has the pkey set here.
+        */
+       return ret;
+}