From 081995cf1347406cb8be8c7ce11fbb256158c83e Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett" Date: Wed, 13 Jul 2022 10:13:34 -0400 Subject: [PATCH 1/1] mm/mmap: Fix __vma_adjust() issue on memory failure In case 6 of vma_merge, two VMAs will be freed, but when allocation fails after the first __vma_adjust() completes and jumps back to the "again" label, then the second VMA is not merged. Upon returning from the __vma_adjust() call with an error code, the calling process assumes the first VMA is still valid, but it has been freed. Reported-by: syzbot+68771c0e74f7bb7804e5@syzkaller.appspotmail.com Fixes: d3ccd17e7c96 ("mm: start tracking VMAs with maple tree") Signed-off-by: Liam R. Howlett --- mm/mmap.c | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/mm/mmap.c b/mm/mmap.c index f25f53d7600d..5ed06870a3f3 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -713,8 +713,6 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, next_next = find_vma(mm, next->vm_end); VM_WARN_ON(remove_next == 2 && end != next_next->vm_end); - /* trim end to next, for case 6 first pass */ - end = next->vm_end; } exporter = next; @@ -762,7 +760,7 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, return error; } } -again: + vma_adjust_trans_huge(orig_vma, start, end, adjust_next); if (mas_preallocate(&mas, vma, GFP_KERNEL)) { @@ -853,6 +851,9 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, if (remove_next && file) { __remove_shared_vm_struct(next, file, mapping); + if (remove_next == 2) + __remove_shared_vm_struct(next_next, file, mapping); + } else if (insert) { /* * split_vma has split insert from vma, and needs @@ -880,47 +881,24 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, } if (remove_next) { +again: if (file) { uprobe_munmap(next, next->vm_start, next->vm_end); fput(file); } if (next->anon_vma) anon_vma_merge(vma, next); + mm->map_count--; mpol_put(vma_policy(next)); - BUG_ON(vma->vm_end < next->vm_end); + if (remove_next != 2) + BUG_ON(vma->vm_end < next->vm_end); vm_area_free(next); - /* - * In mprotect's case 6 (see comments on vma_merge), - * we must remove another next too. It would clutter - * up the code too much to do both in one go. - */ - if (remove_next != 3) { - /* - * If "next" was removed and vma->vm_end was - * expanded (up) over it, in turn - * "next->prev->vm_end" changed and the - * "vma->next" gap must be updated. - */ - next = next_next; - } else { - /* - * For the scope of the comment "next" and - * "vma" considered pre-swap(): if "vma" was - * removed, next->vm_start was expanded (down) - * over it and the "next" gap must be updated. - * Because of the swap() the post-swap() "vma" - * actually points to pre-swap() "next" - * (post-swap() "next" as opposed is now a - * dangling pointer). - */ - next = vma; - } if (remove_next == 2) { - mas_reset(&mas); + /* Case 6 (see vma_merge comments). Clean up next_next. */ remove_next = 1; - end = next->vm_end; + next = next_next; goto again; } } -- 2.35.1