Merge branch 'mm-pkeys-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[cascardo/linux.git] / mm / mprotect.c
index ec91dfd..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"
@@ -353,8 +355,11 @@ fail:
        return error;
 }
 
-SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
-               unsigned long, prot)
+/*
+ * pkey==-1 when doing a legacy mprotect()
+ */
+static int do_mprotect_pkey(unsigned long start, size_t len,
+               unsigned long prot, int pkey)
 {
        unsigned long nstart, end, tmp, reqprot;
        struct vm_area_struct *vma, *prev;
@@ -383,6 +388,14 @@ SYSCALL_DEFINE3(mprotect, 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)
@@ -409,8 +422,9 @@ SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
                prev = vma;
 
        for (nstart = start ; ; ) {
+               unsigned long mask_off_old_flags;
                unsigned long newflags;
-               int pkey = arch_override_mprotect_pkey(vma, prot, -1);
+               int new_vma_pkey;
 
                /* Here we know that vma->vm_start <= nstart < vma->vm_end. */
 
@@ -418,8 +432,17 @@ SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
                if (rier && (vma->vm_flags & VM_MAYEXEC))
                        prot |= PROT_EXEC;
 
-               newflags = calc_vm_prot_bits(prot, pkey);
-               newflags |= (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_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 & ~mask_off_old_flags);
 
                /* newflags >> 4 shift VM_MAY% in place of VM_% */
                if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
@@ -455,3 +478,60 @@ out:
        up_write(&current->mm->mmap_sem);
        return error;
 }
+
+SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
+               unsigned long, prot)
+{
+       return do_mprotect_pkey(start, len, prot, -1);
+}
+
+SYSCALL_DEFINE4(pkey_mprotect, unsigned long, start, size_t, len,
+               unsigned long, prot, int, pkey)
+{
+       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;
+}