tools headers UAPI: Sync linux/prctl.h with the kernel sources
[linux-2.6-microblaze.git] / fs / userfaultfd.c
index 0be8cdd..14f9228 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/sched/signal.h>
 #include <linux/sched/mm.h>
 #include <linux/mm.h>
+#include <linux/mmu_notifier.h>
 #include <linux/poll.h>
 #include <linux/slab.h>
 #include <linux/seq_file.h>
@@ -196,24 +197,21 @@ static inline struct uffd_msg userfault_msg(unsigned long address,
        msg_init(&msg);
        msg.event = UFFD_EVENT_PAGEFAULT;
        msg.arg.pagefault.address = address;
+       /*
+        * These flags indicate why the userfault occurred:
+        * - UFFD_PAGEFAULT_FLAG_WP indicates a write protect fault.
+        * - UFFD_PAGEFAULT_FLAG_MINOR indicates a minor fault.
+        * - Neither of these flags being set indicates a MISSING fault.
+        *
+        * Separately, UFFD_PAGEFAULT_FLAG_WRITE indicates it was a write
+        * fault. Otherwise, it was a read fault.
+        */
        if (flags & FAULT_FLAG_WRITE)
-               /*
-                * If UFFD_FEATURE_PAGEFAULT_FLAG_WP was set in the
-                * uffdio_api.features and UFFD_PAGEFAULT_FLAG_WRITE
-                * was not set in a UFFD_EVENT_PAGEFAULT, it means it
-                * was a read fault, otherwise if set it means it's
-                * a write fault.
-                */
                msg.arg.pagefault.flags |= UFFD_PAGEFAULT_FLAG_WRITE;
        if (reason & VM_UFFD_WP)
-               /*
-                * If UFFD_FEATURE_PAGEFAULT_FLAG_WP was set in the
-                * uffdio_api.features and UFFD_PAGEFAULT_FLAG_WP was
-                * not set in a UFFD_EVENT_PAGEFAULT, it means it was
-                * a missing fault, otherwise if set it means it's a
-                * write protect fault.
-                */
                msg.arg.pagefault.flags |= UFFD_PAGEFAULT_FLAG_WP;
+       if (reason & VM_UFFD_MINOR)
+               msg.arg.pagefault.flags |= UFFD_PAGEFAULT_FLAG_MINOR;
        if (features & UFFD_FEATURE_THREAD_ID)
                msg.arg.pagefault.feat.ptid = task_pid_vnr(current);
        return msg;
@@ -400,8 +398,10 @@ vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)
 
        BUG_ON(ctx->mm != mm);
 
-       VM_BUG_ON(reason & ~(VM_UFFD_MISSING|VM_UFFD_WP));
-       VM_BUG_ON(!(reason & VM_UFFD_MISSING) ^ !!(reason & VM_UFFD_WP));
+       /* Any unrecognized flag is a bug. */
+       VM_BUG_ON(reason & ~__VM_UFFD_FLAGS);
+       /* 0 or > 1 flags set is a bug; we expect exactly 1. */
+       VM_BUG_ON(!reason || (reason & (reason - 1)));
 
        if (ctx->features & UFFD_FEATURE_SIGBUS)
                goto out;
@@ -611,7 +611,7 @@ static void userfaultfd_event_wait_completion(struct userfaultfd_ctx *ctx,
                for (vma = mm->mmap; vma; vma = vma->vm_next)
                        if (vma->vm_userfaultfd_ctx.ctx == release_new_ctx) {
                                vma->vm_userfaultfd_ctx = NULL_VM_UFFD_CTX;
-                               vma->vm_flags &= ~(VM_UFFD_WP | VM_UFFD_MISSING);
+                               vma->vm_flags &= ~__VM_UFFD_FLAGS;
                        }
                mmap_write_unlock(mm);
 
@@ -643,7 +643,7 @@ int dup_userfaultfd(struct vm_area_struct *vma, struct list_head *fcs)
        octx = vma->vm_userfaultfd_ctx.ctx;
        if (!octx || !(octx->features & UFFD_FEATURE_EVENT_FORK)) {
                vma->vm_userfaultfd_ctx = NULL_VM_UFFD_CTX;
-               vma->vm_flags &= ~(VM_UFFD_WP | VM_UFFD_MISSING);
+               vma->vm_flags &= ~__VM_UFFD_FLAGS;
                return 0;
        }
 
@@ -725,7 +725,7 @@ void mremap_userfaultfd_prep(struct vm_area_struct *vma,
        } else {
                /* Drop uffd context if remap feature not enabled */
                vma->vm_userfaultfd_ctx = NULL_VM_UFFD_CTX;
-               vma->vm_flags &= ~(VM_UFFD_WP | VM_UFFD_MISSING);
+               vma->vm_flags &= ~__VM_UFFD_FLAGS;
        }
 }
 
@@ -866,12 +866,12 @@ static int userfaultfd_release(struct inode *inode, struct file *file)
        for (vma = mm->mmap; vma; vma = vma->vm_next) {
                cond_resched();
                BUG_ON(!!vma->vm_userfaultfd_ctx.ctx ^
-                      !!(vma->vm_flags & (VM_UFFD_MISSING | VM_UFFD_WP)));
+                      !!(vma->vm_flags & __VM_UFFD_FLAGS));
                if (vma->vm_userfaultfd_ctx.ctx != ctx) {
                        prev = vma;
                        continue;
                }
-               new_flags = vma->vm_flags & ~(VM_UFFD_MISSING | VM_UFFD_WP);
+               new_flags = vma->vm_flags & ~__VM_UFFD_FLAGS;
                prev = vma_merge(mm, prev, vma->vm_start, vma->vm_end,
                                 new_flags, vma->anon_vma,
                                 vma->vm_file, vma->vm_pgoff,
@@ -1261,9 +1261,19 @@ static inline bool vma_can_userfault(struct vm_area_struct *vma,
                                     unsigned long vm_flags)
 {
        /* FIXME: add WP support to hugetlbfs and shmem */
-       return vma_is_anonymous(vma) ||
-               ((is_vm_hugetlb_page(vma) || vma_is_shmem(vma)) &&
-                !(vm_flags & VM_UFFD_WP));
+       if (vm_flags & VM_UFFD_WP) {
+               if (is_vm_hugetlb_page(vma) || vma_is_shmem(vma))
+                       return false;
+       }
+
+       if (vm_flags & VM_UFFD_MINOR) {
+               /* FIXME: Add minor fault interception for shmem. */
+               if (!is_vm_hugetlb_page(vma))
+                       return false;
+       }
+
+       return vma_is_anonymous(vma) || is_vm_hugetlb_page(vma) ||
+              vma_is_shmem(vma);
 }
 
 static int userfaultfd_register(struct userfaultfd_ctx *ctx,
@@ -1289,14 +1299,19 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
        ret = -EINVAL;
        if (!uffdio_register.mode)
                goto out;
-       if (uffdio_register.mode & ~(UFFDIO_REGISTER_MODE_MISSING|
-                                    UFFDIO_REGISTER_MODE_WP))
+       if (uffdio_register.mode & ~UFFD_API_REGISTER_MODES)
                goto out;
        vm_flags = 0;
        if (uffdio_register.mode & UFFDIO_REGISTER_MODE_MISSING)
                vm_flags |= VM_UFFD_MISSING;
        if (uffdio_register.mode & UFFDIO_REGISTER_MODE_WP)
                vm_flags |= VM_UFFD_WP;
+       if (uffdio_register.mode & UFFDIO_REGISTER_MODE_MINOR) {
+#ifndef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR
+               goto out;
+#endif
+               vm_flags |= VM_UFFD_MINOR;
+       }
 
        ret = validate_range(mm, &uffdio_register.range.start,
                             uffdio_register.range.len);
@@ -1340,7 +1355,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
                cond_resched();
 
                BUG_ON(!!cur->vm_userfaultfd_ctx.ctx ^
-                      !!(cur->vm_flags & (VM_UFFD_MISSING | VM_UFFD_WP)));
+                      !!(cur->vm_flags & __VM_UFFD_FLAGS));
 
                /* check not compatible vmas */
                ret = -EINVAL;
@@ -1420,8 +1435,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
                        start = vma->vm_start;
                vma_end = min(end, vma->vm_end);
 
-               new_flags = (vma->vm_flags &
-                            ~(VM_UFFD_MISSING|VM_UFFD_WP)) | vm_flags;
+               new_flags = (vma->vm_flags & ~__VM_UFFD_FLAGS) | vm_flags;
                prev = vma_merge(mm, prev, start, vma_end, new_flags,
                                 vma->anon_vma, vma->vm_file, vma->vm_pgoff,
                                 vma_policy(vma),
@@ -1449,6 +1463,9 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx,
                vma->vm_flags = new_flags;
                vma->vm_userfaultfd_ctx.ctx = ctx;
 
+               if (is_vm_hugetlb_page(vma) && uffd_disable_huge_pmd_share(vma))
+                       hugetlb_unshare_all_pmds(vma);
+
        skip:
                prev = vma;
                start = vma->vm_end;
@@ -1470,6 +1487,10 @@ out_unlock:
                if (!(uffdio_register.mode & UFFDIO_REGISTER_MODE_WP))
                        ioctls_out &= ~((__u64)1 << _UFFDIO_WRITEPROTECT);
 
+               /* CONTINUE ioctl is only supported for MINOR ranges. */
+               if (!(uffdio_register.mode & UFFDIO_REGISTER_MODE_MINOR))
+                       ioctls_out &= ~((__u64)1 << _UFFDIO_CONTINUE);
+
                /*
                 * Now that we scanned all vmas we can already tell
                 * userland which ioctls methods are guaranteed to
@@ -1540,7 +1561,7 @@ static int userfaultfd_unregister(struct userfaultfd_ctx *ctx,
                cond_resched();
 
                BUG_ON(!!cur->vm_userfaultfd_ctx.ctx ^
-                      !!(cur->vm_flags & (VM_UFFD_MISSING | VM_UFFD_WP)));
+                      !!(cur->vm_flags & __VM_UFFD_FLAGS));
 
                /*
                 * Check not compatible vmas, not strictly required
@@ -1591,7 +1612,7 @@ static int userfaultfd_unregister(struct userfaultfd_ctx *ctx,
                        wake_userfault(vma->vm_userfaultfd_ctx.ctx, &range);
                }
 
-               new_flags = vma->vm_flags & ~(VM_UFFD_MISSING | VM_UFFD_WP);
+               new_flags = vma->vm_flags & ~__VM_UFFD_FLAGS;
                prev = vma_merge(mm, prev, start, vma_end, new_flags,
                                 vma->anon_vma, vma->vm_file, vma->vm_pgoff,
                                 vma_policy(vma),
@@ -1823,6 +1844,66 @@ static int userfaultfd_writeprotect(struct userfaultfd_ctx *ctx,
        return ret;
 }
 
+static int userfaultfd_continue(struct userfaultfd_ctx *ctx, unsigned long arg)
+{
+       __s64 ret;
+       struct uffdio_continue uffdio_continue;
+       struct uffdio_continue __user *user_uffdio_continue;
+       struct userfaultfd_wake_range range;
+
+       user_uffdio_continue = (struct uffdio_continue __user *)arg;
+
+       ret = -EAGAIN;
+       if (READ_ONCE(ctx->mmap_changing))
+               goto out;
+
+       ret = -EFAULT;
+       if (copy_from_user(&uffdio_continue, user_uffdio_continue,
+                          /* don't copy the output fields */
+                          sizeof(uffdio_continue) - (sizeof(__s64))))
+               goto out;
+
+       ret = validate_range(ctx->mm, &uffdio_continue.range.start,
+                            uffdio_continue.range.len);
+       if (ret)
+               goto out;
+
+       ret = -EINVAL;
+       /* double check for wraparound just in case. */
+       if (uffdio_continue.range.start + uffdio_continue.range.len <=
+           uffdio_continue.range.start) {
+               goto out;
+       }
+       if (uffdio_continue.mode & ~UFFDIO_CONTINUE_MODE_DONTWAKE)
+               goto out;
+
+       if (mmget_not_zero(ctx->mm)) {
+               ret = mcopy_continue(ctx->mm, uffdio_continue.range.start,
+                                    uffdio_continue.range.len,
+                                    &ctx->mmap_changing);
+               mmput(ctx->mm);
+       } else {
+               return -ESRCH;
+       }
+
+       if (unlikely(put_user(ret, &user_uffdio_continue->mapped)))
+               return -EFAULT;
+       if (ret < 0)
+               goto out;
+
+       /* len == 0 would wake all */
+       BUG_ON(!ret);
+       range.len = ret;
+       if (!(uffdio_continue.mode & UFFDIO_CONTINUE_MODE_DONTWAKE)) {
+               range.start = uffdio_continue.range.start;
+               wake_userfault(ctx, &range);
+       }
+       ret = range.len == uffdio_continue.range.len ? 0 : -EAGAIN;
+
+out:
+       return ret;
+}
+
 static inline unsigned int uffd_ctx_features(__u64 user_features)
 {
        /*
@@ -1859,6 +1940,9 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx,
                goto err_out;
        /* report all available features and ioctls to userland */
        uffdio_api.features = UFFD_API_FEATURES;
+#ifndef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR
+       uffdio_api.features &= ~UFFD_FEATURE_MINOR_HUGETLBFS;
+#endif
        uffdio_api.ioctls = UFFD_API_IOCTLS;
        ret = -EFAULT;
        if (copy_to_user(buf, &uffdio_api, sizeof(uffdio_api)))
@@ -1907,6 +1991,9 @@ static long userfaultfd_ioctl(struct file *file, unsigned cmd,
        case UFFDIO_WRITEPROTECT:
                ret = userfaultfd_writeprotect(ctx, arg);
                break;
+       case UFFDIO_CONTINUE:
+               ret = userfaultfd_continue(ctx, arg);
+               break;
        }
        return ret;
 }