Commit b717da93 authored by Hugh Dickins's avatar Hugh Dickins Committed by Linus Torvalds

[PATCH] general split_vma hugetlb fix

My recent do_munmap hugetlb fix has proved inadequate.  There are
other places (madvise, mbind, mlock, mprotect) where split_vma is
called.  Only mprotect excludes a hugetlb vma: the others are in
danger of splitting at a misaligned address, causing later BUGs.

So move the ~HPAGE_MASK check from do_munmap to split_vma itself;
and fix up those places (madvise and mlock) which expect split_vma
can fail only with -ENOMEM, and wish to convert that to -EAGAIN.
(It appears genuine that some of these syscalls should be failing
with -ENOMEM and some with -EAGAIN, so respect those behaviours.)

madvise_dontneed doesn't use split_vma, but is equally in danger
of causing a hugetlb BUG via zap_page_range.  Whereas elsewhere the
patch is permissive (allowing the operation on a hugetlb vma even when
pointless, so long as it doesn't missplit it), here we must use -EINVAL
on any hugetlb vma, since a page fault would hit the BUG in its nopage.
Signed-off-by: default avatarHugh Dickins <hugh@veritas.com>
Acked-by: default avatarWilliam Irwin <wli@holomorphy.com>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 88f48c81
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include <linux/mman.h> #include <linux/mman.h>
#include <linux/pagemap.h> #include <linux/pagemap.h>
#include <linux/syscalls.h> #include <linux/syscalls.h>
#include <linux/hugetlb.h>
/* /*
* We can potentially split a vm area into separate * We can potentially split a vm area into separate
...@@ -18,18 +18,18 @@ static long madvise_behavior(struct vm_area_struct * vma, unsigned long start, ...@@ -18,18 +18,18 @@ static long madvise_behavior(struct vm_area_struct * vma, unsigned long start,
unsigned long end, int behavior) unsigned long end, int behavior)
{ {
struct mm_struct * mm = vma->vm_mm; struct mm_struct * mm = vma->vm_mm;
int error; int error = 0;
if (start != vma->vm_start) { if (start != vma->vm_start) {
error = split_vma(mm, vma, start, 1); error = split_vma(mm, vma, start, 1);
if (error) if (error)
return -EAGAIN; goto out;
} }
if (end != vma->vm_end) { if (end != vma->vm_end) {
error = split_vma(mm, vma, end, 0); error = split_vma(mm, vma, end, 0);
if (error) if (error)
return -EAGAIN; goto out;
} }
/* /*
...@@ -48,7 +48,10 @@ static long madvise_behavior(struct vm_area_struct * vma, unsigned long start, ...@@ -48,7 +48,10 @@ static long madvise_behavior(struct vm_area_struct * vma, unsigned long start,
break; break;
} }
return 0; out:
if (error == -ENOMEM)
error = -EAGAIN;
return error;
} }
/* /*
...@@ -94,7 +97,7 @@ static long madvise_willneed(struct vm_area_struct * vma, ...@@ -94,7 +97,7 @@ static long madvise_willneed(struct vm_area_struct * vma,
static long madvise_dontneed(struct vm_area_struct * vma, static long madvise_dontneed(struct vm_area_struct * vma,
unsigned long start, unsigned long end) unsigned long start, unsigned long end)
{ {
if (vma->vm_flags & VM_LOCKED) if ((vma->vm_flags & VM_LOCKED) || is_vm_hugetlb_page(vma))
return -EINVAL; return -EINVAL;
if (unlikely(vma->vm_flags & VM_NONLINEAR)) { if (unlikely(vma->vm_flags & VM_NONLINEAR)) {
......
...@@ -21,18 +21,16 @@ static int mlock_fixup(struct vm_area_struct * vma, ...@@ -21,18 +21,16 @@ static int mlock_fixup(struct vm_area_struct * vma,
goto out; goto out;
if (start != vma->vm_start) { if (start != vma->vm_start) {
if (split_vma(mm, vma, start, 1)) { ret = split_vma(mm, vma, start, 1);
ret = -EAGAIN; if (ret)
goto out; goto out;
} }
}
if (end != vma->vm_end) { if (end != vma->vm_end) {
if (split_vma(mm, vma, end, 0)) { ret = split_vma(mm, vma, end, 0);
ret = -EAGAIN; if (ret)
goto out; goto out;
} }
}
/* /*
* vm_flags is protected by the mmap_sem held in write mode. * vm_flags is protected by the mmap_sem held in write mode.
...@@ -53,6 +51,8 @@ static int mlock_fixup(struct vm_area_struct * vma, ...@@ -53,6 +51,8 @@ static int mlock_fixup(struct vm_area_struct * vma,
vma->vm_mm->locked_vm -= pages; vma->vm_mm->locked_vm -= pages;
out: out:
if (ret == -ENOMEM)
ret = -EAGAIN;
return ret; return ret;
} }
......
...@@ -1747,6 +1747,9 @@ int split_vma(struct mm_struct * mm, struct vm_area_struct * vma, ...@@ -1747,6 +1747,9 @@ int split_vma(struct mm_struct * mm, struct vm_area_struct * vma,
struct mempolicy *pol; struct mempolicy *pol;
struct vm_area_struct *new; struct vm_area_struct *new;
if (is_vm_hugetlb_page(vma) && (addr & ~HPAGE_MASK))
return -EINVAL;
if (mm->map_count >= sysctl_max_map_count) if (mm->map_count >= sysctl_max_map_count)
return -ENOMEM; return -ENOMEM;
...@@ -1821,20 +1824,18 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len) ...@@ -1821,20 +1824,18 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
* places tmp vma above, and higher split_vma places tmp vma below. * places tmp vma above, and higher split_vma places tmp vma below.
*/ */
if (start > mpnt->vm_start) { if (start > mpnt->vm_start) {
if (is_vm_hugetlb_page(mpnt) && (start & ~HPAGE_MASK)) int error = split_vma(mm, mpnt, start, 0);
return -EINVAL; if (error)
if (split_vma(mm, mpnt, start, 0)) return error;
return -ENOMEM;
prev = mpnt; prev = mpnt;
} }
/* Does it split the last one? */ /* Does it split the last one? */
last = find_vma(mm, end); last = find_vma(mm, end);
if (last && end > last->vm_start) { if (last && end > last->vm_start) {
if (is_vm_hugetlb_page(last) && (end & ~HPAGE_MASK)) int error = split_vma(mm, last, end, 1);
return -EINVAL; if (error)
if (split_vma(mm, last, end, 1)) return error;
return -ENOMEM;
} }
mpnt = prev? prev->vm_next: mm->mmap; mpnt = prev? prev->vm_next: mm->mmap;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment