OSDN Git Service

Merge tag 'tag-chrome-platform-for-v5.7' of git://git.kernel.org/pub/scm/linux/kernel...
[tomoyo/tomoyo-test1.git] / mm / mremap.c
index d28f08a..a7e282e 100644 (file)
@@ -133,7 +133,7 @@ static void move_ptes(struct vm_area_struct *vma, pmd_t *old_pmd,
         * such races:
         *
         * - During exec() shift_arg_pages(), we use a specially tagged vma
-        *   which rmap call sites look for using is_vma_temporary_stack().
+        *   which rmap call sites look for using vma_is_temporary_stack().
         *
         * - During mremap(), new_vma is often known to be placed after vma
         *   in rmap traversal order. This ensures rmap will always observe
@@ -318,8 +318,8 @@ unsigned long move_page_tables(struct vm_area_struct *vma,
 static unsigned long move_vma(struct vm_area_struct *vma,
                unsigned long old_addr, unsigned long old_len,
                unsigned long new_len, unsigned long new_addr,
-               bool *locked, struct vm_userfaultfd_ctx *uf,
-               struct list_head *uf_unmap)
+               bool *locked, unsigned long flags,
+               struct vm_userfaultfd_ctx *uf, struct list_head *uf_unmap)
 {
        struct mm_struct *mm = vma->vm_mm;
        struct vm_area_struct *new_vma;
@@ -408,11 +408,32 @@ static unsigned long move_vma(struct vm_area_struct *vma,
        if (unlikely(vma->vm_flags & VM_PFNMAP))
                untrack_pfn_moved(vma);
 
+       if (unlikely(!err && (flags & MREMAP_DONTUNMAP))) {
+               if (vm_flags & VM_ACCOUNT) {
+                       /* Always put back VM_ACCOUNT since we won't unmap */
+                       vma->vm_flags |= VM_ACCOUNT;
+
+                       vm_acct_memory(vma_pages(new_vma));
+               }
+
+               /* We always clear VM_LOCKED[ONFAULT] on the old vma */
+               vma->vm_flags &= VM_LOCKED_CLEAR_MASK;
+
+               /* Because we won't unmap we don't need to touch locked_vm */
+               goto out;
+       }
+
        if (do_munmap(mm, old_addr, old_len, uf_unmap) < 0) {
                /* OOM: unable to split vma, just get accounts right */
                vm_unacct_memory(excess >> PAGE_SHIFT);
                excess = 0;
        }
+
+       if (vm_flags & VM_LOCKED) {
+               mm->locked_vm += new_len >> PAGE_SHIFT;
+               *locked = true;
+       }
+out:
        mm->hiwater_vm = hiwater_vm;
 
        /* Restore VM_ACCOUNT if one or two pieces of vma left */
@@ -422,16 +443,12 @@ static unsigned long move_vma(struct vm_area_struct *vma,
                        vma->vm_next->vm_flags |= VM_ACCOUNT;
        }
 
-       if (vm_flags & VM_LOCKED) {
-               mm->locked_vm += new_len >> PAGE_SHIFT;
-               *locked = true;
-       }
-
        return new_addr;
 }
 
 static struct vm_area_struct *vma_to_resize(unsigned long addr,
-       unsigned long old_len, unsigned long new_len, unsigned long *p)
+       unsigned long old_len, unsigned long new_len, unsigned long flags,
+       unsigned long *p)
 {
        struct mm_struct *mm = current->mm;
        struct vm_area_struct *vma = find_vma(mm, addr);
@@ -453,6 +470,10 @@ static struct vm_area_struct *vma_to_resize(unsigned long addr,
                return ERR_PTR(-EINVAL);
        }
 
+       if (flags & MREMAP_DONTUNMAP && (!vma_is_anonymous(vma) ||
+                       vma->vm_flags & VM_SHARED))
+               return ERR_PTR(-EINVAL);
+
        if (is_vm_hugetlb_page(vma))
                return ERR_PTR(-EINVAL);
 
@@ -497,7 +518,7 @@ static struct vm_area_struct *vma_to_resize(unsigned long addr,
 
 static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
                unsigned long new_addr, unsigned long new_len, bool *locked,
-               struct vm_userfaultfd_ctx *uf,
+               unsigned long flags, struct vm_userfaultfd_ctx *uf,
                struct list_head *uf_unmap_early,
                struct list_head *uf_unmap)
 {
@@ -505,7 +526,7 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
        struct vm_area_struct *vma;
        unsigned long ret = -EINVAL;
        unsigned long charged = 0;
-       unsigned long map_flags;
+       unsigned long map_flags = 0;
 
        if (offset_in_page(new_addr))
                goto out;
@@ -534,9 +555,11 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
        if ((mm->map_count + 2) >= sysctl_max_map_count - 3)
                return -ENOMEM;
 
-       ret = do_munmap(mm, new_addr, new_len, uf_unmap_early);
-       if (ret)
-               goto out;
+       if (flags & MREMAP_FIXED) {
+               ret = do_munmap(mm, new_addr, new_len, uf_unmap_early);
+               if (ret)
+                       goto out;
+       }
 
        if (old_len >= new_len) {
                ret = do_munmap(mm, addr+new_len, old_len - new_len, uf_unmap);
@@ -545,13 +568,22 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
                old_len = new_len;
        }
 
-       vma = vma_to_resize(addr, old_len, new_len, &charged);
+       vma = vma_to_resize(addr, old_len, new_len, flags, &charged);
        if (IS_ERR(vma)) {
                ret = PTR_ERR(vma);
                goto out;
        }
 
-       map_flags = MAP_FIXED;
+       /* MREMAP_DONTUNMAP expands by old_len since old_len == new_len */
+       if (flags & MREMAP_DONTUNMAP &&
+               !may_expand_vm(mm, vma->vm_flags, old_len >> PAGE_SHIFT)) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       if (flags & MREMAP_FIXED)
+               map_flags |= MAP_FIXED;
+
        if (vma->vm_flags & VM_MAYSHARE)
                map_flags |= MAP_SHARED;
 
@@ -561,10 +593,16 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
        if (IS_ERR_VALUE(ret))
                goto out1;
 
-       ret = move_vma(vma, addr, old_len, new_len, new_addr, locked, uf,
+       /* We got a new mapping */
+       if (!(flags & MREMAP_FIXED))
+               new_addr = ret;
+
+       ret = move_vma(vma, addr, old_len, new_len, new_addr, locked, flags, uf,
                       uf_unmap);
+
        if (!(offset_in_page(ret)))
                goto out;
+
 out1:
        vm_unacct_memory(charged);
 
@@ -618,12 +656,21 @@ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
         */
        addr = untagged_addr(addr);
 
-       if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE))
+       if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP))
                return ret;
 
        if (flags & MREMAP_FIXED && !(flags & MREMAP_MAYMOVE))
                return ret;
 
+       /*
+        * MREMAP_DONTUNMAP is always a move and it does not allow resizing
+        * in the process.
+        */
+       if (flags & MREMAP_DONTUNMAP &&
+                       (!(flags & MREMAP_MAYMOVE) || old_len != new_len))
+               return ret;
+
+
        if (offset_in_page(addr))
                return ret;
 
@@ -641,9 +688,10 @@ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
        if (down_write_killable(&current->mm->mmap_sem))
                return -EINTR;
 
-       if (flags & MREMAP_FIXED) {
+       if (flags & (MREMAP_FIXED | MREMAP_DONTUNMAP)) {
                ret = mremap_to(addr, old_len, new_addr, new_len,
-                               &locked, &uf, &uf_unmap_early, &uf_unmap);
+                               &locked, flags, &uf, &uf_unmap_early,
+                               &uf_unmap);
                goto out;
        }
 
@@ -671,7 +719,7 @@ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
        /*
         * Ok, we need to grow..
         */
-       vma = vma_to_resize(addr, old_len, new_len, &charged);
+       vma = vma_to_resize(addr, old_len, new_len, flags, &charged);
        if (IS_ERR(vma)) {
                ret = PTR_ERR(vma);
                goto out;
@@ -721,7 +769,7 @@ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
                }
 
                ret = move_vma(vma, addr, old_len, new_len, new_addr,
-                              &locked, &uf, &uf_unmap);
+                              &locked, flags, &uf, &uf_unmap);
        }
 out:
        if (offset_in_page(ret)) {