summaryrefslogtreecommitdiff
path: root/kernel/fork.c
diff options
context:
space:
mode:
authorAndrew Morton <akpm@osdl.org>2004-03-07 22:42:37 -0800
committerLinus Torvalds <torvalds@ppc970.osdl.org>2004-03-07 22:42:37 -0800
commitbc3d0059577fa9c59b2188e649e2a109b3fdde95 (patch)
tree9968c9d3623501e71e097cde21173fda402a13f5 /kernel/fork.c
parentecad96d53e6056d1bc2c1badec28b4ac68503fe9 (diff)
[PATCH] vma corruption fix
From: Hugh Dickins <hugh@veritas.com> Fixes bugzilla #2219 fork's dup_mmap leaves child mm_rb as copied from parent mm while doing all the copy_page_ranges, and then calls build_mmap_rb without holding page_table_lock. try_to_unmap_one's find_vma (holding page_table_lock not mmap_sem) coming on another cpu may cause mm mayhem. It may leave the child's mmap_cache pointing to a vma of the parent mm. When the parent exits and the child faults, quite what happens rather depends on what junk then inhabits vm_page_prot, which gets set in the page table, with page_add_rmap adding the ptep, but junk pte likely to fail the tests for page_remove_rmap. Eventually the child exits, the page table is freed and try_to_unmap_one oopses on null ptep_to_mm (but in a kernel with rss limiting, usually page_referenced hits the null ptep_to_mm first). This took me days and days to unravel! Big thanks to Matthieu for reporting it with a good test case.
Diffstat (limited to 'kernel/fork.c')
-rw-r--r--kernel/fork.c13
1 files changed, 11 insertions, 2 deletions
diff --git a/kernel/fork.c b/kernel/fork.c
index 1c334ebca506..3b17a249c50d 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -265,6 +265,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig)
static inline int dup_mmap(struct mm_struct * mm, struct mm_struct * oldmm)
{
struct vm_area_struct * mpnt, *tmp, **pprev;
+ struct rb_node **rb_link, *rb_parent;
int retval;
unsigned long charge = 0;
@@ -277,6 +278,9 @@ static inline int dup_mmap(struct mm_struct * mm, struct mm_struct * oldmm)
mm->map_count = 0;
mm->rss = 0;
cpus_clear(mm->cpu_vm_mask);
+ mm->mm_rb = RB_ROOT;
+ rb_link = &mm->mm_rb.rb_node;
+ rb_parent = NULL;
pprev = &mm->mmap;
/*
@@ -324,11 +328,17 @@ static inline int dup_mmap(struct mm_struct * mm, struct mm_struct * oldmm)
/*
* Link in the new vma and copy the page table entries:
- * link in first so that swapoff can see swap entries.
+ * link in first so that swapoff can see swap entries,
+ * and try_to_unmap_one's find_vma find the new vma.
*/
spin_lock(&mm->page_table_lock);
*pprev = tmp;
pprev = &tmp->vm_next;
+
+ __vma_link_rb(mm, tmp, rb_link, rb_parent);
+ rb_link = &tmp->vm_rb.rb_right;
+ rb_parent = &tmp->vm_rb;
+
mm->map_count++;
retval = copy_page_range(mm, current->mm, tmp);
spin_unlock(&mm->page_table_lock);
@@ -340,7 +350,6 @@ static inline int dup_mmap(struct mm_struct * mm, struct mm_struct * oldmm)
goto fail;
}
retval = 0;
- build_mmap_rb(mm);
out:
flush_tlb_mm(current->mm);