| // SPDX-License-Identifier: GPL-2.0-or-later |
| |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include "maple-shared.h" |
| #include "vma_internal.h" |
| |
| /* Include so header guard set. */ |
| #include "../../../mm/vma.h" |
| |
| static bool fail_prealloc; |
| |
| /* Then override vma_iter_prealloc() so we can choose to fail it. */ |
| #define vma_iter_prealloc(vmi, vma) \ |
| (fail_prealloc ? -ENOMEM : mas_preallocate(&(vmi)->mas, (vma), GFP_KERNEL)) |
| |
| /* |
| * Directly import the VMA implementation here. Our vma_internal.h wrapper |
| * provides userland-equivalent functionality for everything vma.c uses. |
| */ |
| #include "../../../mm/vma.c" |
| |
| const struct vm_operations_struct vma_dummy_vm_ops; |
| static struct anon_vma dummy_anon_vma; |
| |
| #define ASSERT_TRUE(_expr) \ |
| do { \ |
| if (!(_expr)) { \ |
| fprintf(stderr, \ |
| "Assert FAILED at %s:%d:%s(): %s is FALSE.\n", \ |
| __FILE__, __LINE__, __FUNCTION__, #_expr); \ |
| return false; \ |
| } \ |
| } while (0) |
| #define ASSERT_FALSE(_expr) ASSERT_TRUE(!(_expr)) |
| #define ASSERT_EQ(_val1, _val2) ASSERT_TRUE((_val1) == (_val2)) |
| #define ASSERT_NE(_val1, _val2) ASSERT_TRUE((_val1) != (_val2)) |
| |
| static struct task_struct __current; |
| |
| struct task_struct *get_current(void) |
| { |
| return &__current; |
| } |
| |
| /* Helper function to simply allocate a VMA. */ |
| static struct vm_area_struct *alloc_vma(struct mm_struct *mm, |
| unsigned long start, |
| unsigned long end, |
| pgoff_t pgoff, |
| vm_flags_t flags) |
| { |
| struct vm_area_struct *ret = vm_area_alloc(mm); |
| |
| if (ret == NULL) |
| return NULL; |
| |
| ret->vm_start = start; |
| ret->vm_end = end; |
| ret->vm_pgoff = pgoff; |
| ret->__vm_flags = flags; |
| |
| return ret; |
| } |
| |
| /* Helper function to allocate a VMA and link it to the tree. */ |
| static struct vm_area_struct *alloc_and_link_vma(struct mm_struct *mm, |
| unsigned long start, |
| unsigned long end, |
| pgoff_t pgoff, |
| vm_flags_t flags) |
| { |
| struct vm_area_struct *vma = alloc_vma(mm, start, end, pgoff, flags); |
| |
| if (vma == NULL) |
| return NULL; |
| |
| if (vma_link(mm, vma)) { |
| vm_area_free(vma); |
| return NULL; |
| } |
| |
| /* |
| * Reset this counter which we use to track whether writes have |
| * begun. Linking to the tree will have caused this to be incremented, |
| * which means we will get a false positive otherwise. |
| */ |
| vma->vm_lock_seq = -1; |
| |
| return vma; |
| } |
| |
| /* Helper function which provides a wrapper around a merge new VMA operation. */ |
| static struct vm_area_struct *merge_new(struct vma_merge_struct *vmg) |
| { |
| /* |
| * For convenience, get prev and next VMAs. Which the new VMA operation |
| * requires. |
| */ |
| vmg->next = vma_next(vmg->vmi); |
| vmg->prev = vma_prev(vmg->vmi); |
| vma_iter_next_range(vmg->vmi); |
| |
| return vma_merge_new_range(vmg); |
| } |
| |
| /* |
| * Helper function which provides a wrapper around a merge existing VMA |
| * operation. |
| */ |
| static struct vm_area_struct *merge_existing(struct vma_merge_struct *vmg) |
| { |
| return vma_merge_existing_range(vmg); |
| } |
| |
| /* |
| * Helper function which provides a wrapper around the expansion of an existing |
| * VMA. |
| */ |
| static int expand_existing(struct vma_merge_struct *vmg) |
| { |
| return vma_expand(vmg); |
| } |
| |
| /* |
| * Helper function to reset merge state the associated VMA iterator to a |
| * specified new range. |
| */ |
| static void vmg_set_range(struct vma_merge_struct *vmg, unsigned long start, |
| unsigned long end, pgoff_t pgoff, vm_flags_t flags) |
| { |
| vma_iter_set(vmg->vmi, start); |
| |
| vmg->prev = NULL; |
| vmg->next = NULL; |
| vmg->vma = NULL; |
| |
| vmg->start = start; |
| vmg->end = end; |
| vmg->pgoff = pgoff; |
| vmg->flags = flags; |
| } |
| |
| /* |
| * Helper function to try to merge a new VMA. |
| * |
| * Update vmg and the iterator for it and try to merge, otherwise allocate a new |
| * VMA, link it to the maple tree and return it. |
| */ |
| static struct vm_area_struct *try_merge_new_vma(struct mm_struct *mm, |
| struct vma_merge_struct *vmg, |
| unsigned long start, unsigned long end, |
| pgoff_t pgoff, vm_flags_t flags, |
| bool *was_merged) |
| { |
| struct vm_area_struct *merged; |
| |
| vmg_set_range(vmg, start, end, pgoff, flags); |
| |
| merged = merge_new(vmg); |
| if (merged) { |
| *was_merged = true; |
| ASSERT_EQ(vmg->state, VMA_MERGE_SUCCESS); |
| return merged; |
| } |
| |
| *was_merged = false; |
| |
| ASSERT_EQ(vmg->state, VMA_MERGE_NOMERGE); |
| |
| return alloc_and_link_vma(mm, start, end, pgoff, flags); |
| } |
| |
| /* |
| * Helper function to reset the dummy anon_vma to indicate it has not been |
| * duplicated. |
| */ |
| static void reset_dummy_anon_vma(void) |
| { |
| dummy_anon_vma.was_cloned = false; |
| dummy_anon_vma.was_unlinked = false; |
| } |
| |
| /* |
| * Helper function to remove all VMAs and destroy the maple tree associated with |
| * a virtual address space. Returns a count of VMAs in the tree. |
| */ |
| static int cleanup_mm(struct mm_struct *mm, struct vma_iterator *vmi) |
| { |
| struct vm_area_struct *vma; |
| int count = 0; |
| |
| fail_prealloc = false; |
| reset_dummy_anon_vma(); |
| |
| vma_iter_set(vmi, 0); |
| for_each_vma(*vmi, vma) { |
| vm_area_free(vma); |
| count++; |
| } |
| |
| mtree_destroy(&mm->mm_mt); |
| mm->map_count = 0; |
| return count; |
| } |
| |
| /* Helper function to determine if VMA has had vma_start_write() performed. */ |
| static bool vma_write_started(struct vm_area_struct *vma) |
| { |
| int seq = vma->vm_lock_seq; |
| |
| /* We reset after each check. */ |
| vma->vm_lock_seq = -1; |
| |
| /* The vma_start_write() stub simply increments this value. */ |
| return seq > -1; |
| } |
| |
| /* Helper function providing a dummy vm_ops->close() method.*/ |
| static void dummy_close(struct vm_area_struct *) |
| { |
| } |
| |
| static bool test_simple_merge(void) |
| { |
| struct vm_area_struct *vma; |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| struct vm_area_struct *vma_left = alloc_vma(&mm, 0, 0x1000, 0, flags); |
| struct vm_area_struct *vma_right = alloc_vma(&mm, 0x2000, 0x3000, 2, flags); |
| VMA_ITERATOR(vmi, &mm, 0x1000); |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| .start = 0x1000, |
| .end = 0x2000, |
| .flags = flags, |
| .pgoff = 1, |
| }; |
| |
| ASSERT_FALSE(vma_link(&mm, vma_left)); |
| ASSERT_FALSE(vma_link(&mm, vma_right)); |
| |
| vma = merge_new(&vmg); |
| ASSERT_NE(vma, NULL); |
| |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x3000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_EQ(vma->vm_flags, flags); |
| |
| vm_area_free(vma); |
| mtree_destroy(&mm.mm_mt); |
| |
| return true; |
| } |
| |
| static bool test_simple_modify(void) |
| { |
| struct vm_area_struct *vma; |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| struct vm_area_struct *init_vma = alloc_vma(&mm, 0, 0x3000, 0, flags); |
| VMA_ITERATOR(vmi, &mm, 0x1000); |
| |
| ASSERT_FALSE(vma_link(&mm, init_vma)); |
| |
| /* |
| * The flags will not be changed, the vma_modify_flags() function |
| * performs the merge/split only. |
| */ |
| vma = vma_modify_flags(&vmi, init_vma, init_vma, |
| 0x1000, 0x2000, VM_READ | VM_MAYREAD); |
| ASSERT_NE(vma, NULL); |
| /* We modify the provided VMA, and on split allocate new VMAs. */ |
| ASSERT_EQ(vma, init_vma); |
| |
| ASSERT_EQ(vma->vm_start, 0x1000); |
| ASSERT_EQ(vma->vm_end, 0x2000); |
| ASSERT_EQ(vma->vm_pgoff, 1); |
| |
| /* |
| * Now walk through the three split VMAs and make sure they are as |
| * expected. |
| */ |
| |
| vma_iter_set(&vmi, 0); |
| vma = vma_iter_load(&vmi); |
| |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x1000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| |
| vm_area_free(vma); |
| vma_iter_clear(&vmi); |
| |
| vma = vma_next(&vmi); |
| |
| ASSERT_EQ(vma->vm_start, 0x1000); |
| ASSERT_EQ(vma->vm_end, 0x2000); |
| ASSERT_EQ(vma->vm_pgoff, 1); |
| |
| vm_area_free(vma); |
| vma_iter_clear(&vmi); |
| |
| vma = vma_next(&vmi); |
| |
| ASSERT_EQ(vma->vm_start, 0x2000); |
| ASSERT_EQ(vma->vm_end, 0x3000); |
| ASSERT_EQ(vma->vm_pgoff, 2); |
| |
| vm_area_free(vma); |
| mtree_destroy(&mm.mm_mt); |
| |
| return true; |
| } |
| |
| static bool test_simple_expand(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| struct vm_area_struct *vma = alloc_vma(&mm, 0, 0x1000, 0, flags); |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vma_merge_struct vmg = { |
| .vmi = &vmi, |
| .vma = vma, |
| .start = 0, |
| .end = 0x3000, |
| .pgoff = 0, |
| }; |
| |
| ASSERT_FALSE(vma_link(&mm, vma)); |
| |
| ASSERT_FALSE(expand_existing(&vmg)); |
| |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x3000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| |
| vm_area_free(vma); |
| mtree_destroy(&mm.mm_mt); |
| |
| return true; |
| } |
| |
| static bool test_simple_shrink(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| struct vm_area_struct *vma = alloc_vma(&mm, 0, 0x3000, 0, flags); |
| VMA_ITERATOR(vmi, &mm, 0); |
| |
| ASSERT_FALSE(vma_link(&mm, vma)); |
| |
| ASSERT_FALSE(vma_shrink(&vmi, vma, 0, 0x1000, 0)); |
| |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x1000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| |
| vm_area_free(vma); |
| mtree_destroy(&mm.mm_mt); |
| |
| return true; |
| } |
| |
| static bool test_merge_new(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| struct anon_vma_chain dummy_anon_vma_chain_a = { |
| .anon_vma = &dummy_anon_vma, |
| }; |
| struct anon_vma_chain dummy_anon_vma_chain_b = { |
| .anon_vma = &dummy_anon_vma, |
| }; |
| struct anon_vma_chain dummy_anon_vma_chain_c = { |
| .anon_vma = &dummy_anon_vma, |
| }; |
| struct anon_vma_chain dummy_anon_vma_chain_d = { |
| .anon_vma = &dummy_anon_vma, |
| }; |
| const struct vm_operations_struct vm_ops = { |
| .close = dummy_close, |
| }; |
| int count; |
| struct vm_area_struct *vma, *vma_a, *vma_b, *vma_c, *vma_d; |
| bool merged; |
| |
| /* |
| * 0123456789abc |
| * AA B CC |
| */ |
| vma_a = alloc_and_link_vma(&mm, 0, 0x2000, 0, flags); |
| ASSERT_NE(vma_a, NULL); |
| /* We give each VMA a single avc so we can test anon_vma duplication. */ |
| INIT_LIST_HEAD(&vma_a->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain_a.same_vma, &vma_a->anon_vma_chain); |
| |
| vma_b = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, flags); |
| ASSERT_NE(vma_b, NULL); |
| INIT_LIST_HEAD(&vma_b->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain_b.same_vma, &vma_b->anon_vma_chain); |
| |
| vma_c = alloc_and_link_vma(&mm, 0xb000, 0xc000, 0xb, flags); |
| ASSERT_NE(vma_c, NULL); |
| INIT_LIST_HEAD(&vma_c->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain_c.same_vma, &vma_c->anon_vma_chain); |
| |
| /* |
| * NO merge. |
| * |
| * 0123456789abc |
| * AA B ** CC |
| */ |
| vma_d = try_merge_new_vma(&mm, &vmg, 0x7000, 0x9000, 7, flags, &merged); |
| ASSERT_NE(vma_d, NULL); |
| INIT_LIST_HEAD(&vma_d->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain_d.same_vma, &vma_d->anon_vma_chain); |
| ASSERT_FALSE(merged); |
| ASSERT_EQ(mm.map_count, 4); |
| |
| /* |
| * Merge BOTH sides. |
| * |
| * 0123456789abc |
| * AA*B DD CC |
| */ |
| vma_a->vm_ops = &vm_ops; /* This should have no impact. */ |
| vma_b->anon_vma = &dummy_anon_vma; |
| vma = try_merge_new_vma(&mm, &vmg, 0x2000, 0x3000, 2, flags, &merged); |
| ASSERT_EQ(vma, vma_a); |
| /* Merge with A, delete B. */ |
| ASSERT_TRUE(merged); |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x4000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 3); |
| |
| /* |
| * Merge to PREVIOUS VMA. |
| * |
| * 0123456789abc |
| * AAAA* DD CC |
| */ |
| vma = try_merge_new_vma(&mm, &vmg, 0x4000, 0x5000, 4, flags, &merged); |
| ASSERT_EQ(vma, vma_a); |
| /* Extend A. */ |
| ASSERT_TRUE(merged); |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x5000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 3); |
| |
| /* |
| * Merge to NEXT VMA. |
| * |
| * 0123456789abc |
| * AAAAA *DD CC |
| */ |
| vma_d->anon_vma = &dummy_anon_vma; |
| vma_d->vm_ops = &vm_ops; /* This should have no impact. */ |
| vma = try_merge_new_vma(&mm, &vmg, 0x6000, 0x7000, 6, flags, &merged); |
| ASSERT_EQ(vma, vma_d); |
| /* Prepend. */ |
| ASSERT_TRUE(merged); |
| ASSERT_EQ(vma->vm_start, 0x6000); |
| ASSERT_EQ(vma->vm_end, 0x9000); |
| ASSERT_EQ(vma->vm_pgoff, 6); |
| ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 3); |
| |
| /* |
| * Merge BOTH sides. |
| * |
| * 0123456789abc |
| * AAAAA*DDD CC |
| */ |
| vma_d->vm_ops = NULL; /* This would otherwise degrade the merge. */ |
| vma = try_merge_new_vma(&mm, &vmg, 0x5000, 0x6000, 5, flags, &merged); |
| ASSERT_EQ(vma, vma_a); |
| /* Merge with A, delete D. */ |
| ASSERT_TRUE(merged); |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x9000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 2); |
| |
| /* |
| * Merge to NEXT VMA. |
| * |
| * 0123456789abc |
| * AAAAAAAAA *CC |
| */ |
| vma_c->anon_vma = &dummy_anon_vma; |
| vma = try_merge_new_vma(&mm, &vmg, 0xa000, 0xb000, 0xa, flags, &merged); |
| ASSERT_EQ(vma, vma_c); |
| /* Prepend C. */ |
| ASSERT_TRUE(merged); |
| ASSERT_EQ(vma->vm_start, 0xa000); |
| ASSERT_EQ(vma->vm_end, 0xc000); |
| ASSERT_EQ(vma->vm_pgoff, 0xa); |
| ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 2); |
| |
| /* |
| * Merge BOTH sides. |
| * |
| * 0123456789abc |
| * AAAAAAAAA*CCC |
| */ |
| vma = try_merge_new_vma(&mm, &vmg, 0x9000, 0xa000, 0x9, flags, &merged); |
| ASSERT_EQ(vma, vma_a); |
| /* Extend A and delete C. */ |
| ASSERT_TRUE(merged); |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0xc000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 1); |
| |
| /* |
| * Final state. |
| * |
| * 0123456789abc |
| * AAAAAAAAAAAAA |
| */ |
| |
| count = 0; |
| vma_iter_set(&vmi, 0); |
| for_each_vma(vmi, vma) { |
| ASSERT_NE(vma, NULL); |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0xc000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_EQ(vma->anon_vma, &dummy_anon_vma); |
| |
| vm_area_free(vma); |
| count++; |
| } |
| |
| /* Should only have one VMA left (though freed) after all is done.*/ |
| ASSERT_EQ(count, 1); |
| |
| mtree_destroy(&mm.mm_mt); |
| return true; |
| } |
| |
| static bool test_vma_merge_special_flags(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| vm_flags_t special_flags[] = { VM_IO, VM_DONTEXPAND, VM_PFNMAP, VM_MIXEDMAP }; |
| vm_flags_t all_special_flags = 0; |
| int i; |
| struct vm_area_struct *vma_left, *vma; |
| |
| /* Make sure there aren't new VM_SPECIAL flags. */ |
| for (i = 0; i < ARRAY_SIZE(special_flags); i++) { |
| all_special_flags |= special_flags[i]; |
| } |
| ASSERT_EQ(all_special_flags, VM_SPECIAL); |
| |
| /* |
| * 01234 |
| * AAA |
| */ |
| vma_left = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| ASSERT_NE(vma_left, NULL); |
| |
| /* 1. Set up new VMA with special flag that would otherwise merge. */ |
| |
| /* |
| * 01234 |
| * AAA* |
| * |
| * This should merge if not for the VM_SPECIAL flag. |
| */ |
| vmg_set_range(&vmg, 0x3000, 0x4000, 3, flags); |
| for (i = 0; i < ARRAY_SIZE(special_flags); i++) { |
| vm_flags_t special_flag = special_flags[i]; |
| |
| vma_left->__vm_flags = flags | special_flag; |
| vmg.flags = flags | special_flag; |
| vma = merge_new(&vmg); |
| ASSERT_EQ(vma, NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| } |
| |
| /* 2. Modify VMA with special flag that would otherwise merge. */ |
| |
| /* |
| * 01234 |
| * AAAB |
| * |
| * Create a VMA to modify. |
| */ |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, flags); |
| ASSERT_NE(vma, NULL); |
| vmg.vma = vma; |
| |
| for (i = 0; i < ARRAY_SIZE(special_flags); i++) { |
| vm_flags_t special_flag = special_flags[i]; |
| |
| vma_left->__vm_flags = flags | special_flag; |
| vmg.flags = flags | special_flag; |
| vma = merge_existing(&vmg); |
| ASSERT_EQ(vma, NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| } |
| |
| cleanup_mm(&mm, &vmi); |
| return true; |
| } |
| |
| static bool test_vma_merge_with_close(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| const struct vm_operations_struct vm_ops = { |
| .close = dummy_close, |
| }; |
| struct vm_area_struct *vma_prev, *vma_next, *vma; |
| |
| /* |
| * When merging VMAs we are not permitted to remove any VMA that has a |
| * vm_ops->close() hook. |
| * |
| * Considering the two possible adjacent VMAs to which a VMA can be |
| * merged: |
| * |
| * [ prev ][ vma ][ next ] |
| * |
| * In no case will we need to delete prev. If the operation is |
| * mergeable, then prev will be extended with one or both of vma and |
| * next deleted. |
| * |
| * As a result, during initial mergeability checks, only |
| * can_vma_merge_before() (which implies the VMA being merged with is |
| * 'next' as shown above) bothers to check to see whether the next VMA |
| * has a vm_ops->close() callback that will need to be called when |
| * removed. |
| * |
| * If it does, then we cannot merge as the resources that the close() |
| * operation potentially clears down are tied only to the existing VMA |
| * range and we have no way of extending those to the nearly merged one. |
| * |
| * We must consider two scenarios: |
| * |
| * A. |
| * |
| * vm_ops->close: - - !NULL |
| * [ prev ][ vma ][ next ] |
| * |
| * Where prev may or may not be present/mergeable. |
| * |
| * This is picked up by a specific check in can_vma_merge_before(). |
| * |
| * B. |
| * |
| * vm_ops->close: - !NULL |
| * [ prev ][ vma ] |
| * |
| * Where prev and vma are present and mergeable. |
| * |
| * This is picked up by a specific check in the modified VMA merge. |
| * |
| * IMPORTANT NOTE: We make the assumption that the following case: |
| * |
| * - !NULL NULL |
| * [ prev ][ vma ][ next ] |
| * |
| * Cannot occur, because vma->vm_ops being the same implies the same |
| * vma->vm_file, and therefore this would mean that next->vm_ops->close |
| * would be set too, and thus scenario A would pick this up. |
| */ |
| |
| /* |
| * The only case of a new VMA merge that results in a VMA being deleted |
| * is one where both the previous and next VMAs are merged - in this |
| * instance the next VMA is deleted, and the previous VMA is extended. |
| * |
| * If we are unable to do so, we reduce the operation to simply |
| * extending the prev VMA and not merging next. |
| * |
| * 0123456789 |
| * PPP**NNNN |
| * -> |
| * 0123456789 |
| * PPPPPPNNN |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x5000, 0x9000, 5, flags); |
| vma_next->vm_ops = &vm_ops; |
| |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| ASSERT_EQ(merge_new(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x5000); |
| ASSERT_EQ(vma_prev->vm_pgoff, 0); |
| |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| /* |
| * When modifying an existing VMA there are further cases where we |
| * delete VMAs. |
| * |
| * <> |
| * 0123456789 |
| * PPPVV |
| * |
| * In this instance, if vma has a close hook, the merge simply cannot |
| * proceed. |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma->vm_ops = &vm_ops; |
| |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| /* |
| * The VMA being modified in a way that would otherwise merge should |
| * also fail. |
| */ |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| /* |
| * This case is mirrored if merging with next. |
| * |
| * <> |
| * 0123456789 |
| * VVNNNN |
| * |
| * In this instance, if vma has a close hook, the merge simply cannot |
| * proceed. |
| */ |
| |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x5000, 0x9000, 5, flags); |
| vma->vm_ops = &vm_ops; |
| |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.vma = vma; |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| /* |
| * Initially this is misapprehended as an out of memory report, as the |
| * close() check is handled in the same way as anon_vma duplication |
| * failures, however a subsequent patch resolves this. |
| */ |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| /* |
| * Finally, we consider two variants of the case where we modify a VMA |
| * to merge with both the previous and next VMAs. |
| * |
| * The first variant is where vma has a close hook. In this instance, no |
| * merge can proceed. |
| * |
| * <> |
| * 0123456789 |
| * PPPVVNNNN |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x5000, 0x9000, 5, flags); |
| vma->vm_ops = &vm_ops; |
| |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 3); |
| |
| /* |
| * The second variant is where next has a close hook. In this instance, |
| * we reduce the operation to a merge between prev and vma. |
| * |
| * <> |
| * 0123456789 |
| * PPPVVNNNN |
| * -> |
| * 0123456789 |
| * PPPPPNNNN |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x5000, 0x9000, 5, flags); |
| vma_next->vm_ops = &vm_ops; |
| |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x5000); |
| ASSERT_EQ(vma_prev->vm_pgoff, 0); |
| |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| return true; |
| } |
| |
| static bool test_vma_merge_new_with_close(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| struct vm_area_struct *vma_prev = alloc_and_link_vma(&mm, 0, 0x2000, 0, flags); |
| struct vm_area_struct *vma_next = alloc_and_link_vma(&mm, 0x5000, 0x7000, 5, flags); |
| const struct vm_operations_struct vm_ops = { |
| .close = dummy_close, |
| }; |
| struct vm_area_struct *vma; |
| |
| /* |
| * We should allow the partial merge of a proposed new VMA if the |
| * surrounding VMAs have vm_ops->close() hooks (but are otherwise |
| * compatible), e.g.: |
| * |
| * New VMA |
| * A v-------v B |
| * |-----| |-----| |
| * close close |
| * |
| * Since the rule is to not DELETE a VMA with a close operation, this |
| * should be permitted, only rather than expanding A and deleting B, we |
| * should simply expand A and leave B intact, e.g.: |
| * |
| * New VMA |
| * A B |
| * |------------||-----| |
| * close close |
| */ |
| |
| /* Have prev and next have a vm_ops->close() hook. */ |
| vma_prev->vm_ops = &vm_ops; |
| vma_next->vm_ops = &vm_ops; |
| |
| vmg_set_range(&vmg, 0x2000, 0x5000, 2, flags); |
| vma = merge_new(&vmg); |
| ASSERT_NE(vma, NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x5000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_EQ(vma->vm_ops, &vm_ops); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 2); |
| |
| cleanup_mm(&mm, &vmi); |
| return true; |
| } |
| |
| static bool test_merge_existing(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vm_area_struct *vma, *vma_prev, *vma_next; |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| const struct vm_operations_struct vm_ops = { |
| .close = dummy_close, |
| }; |
| |
| /* |
| * Merge right case - partial span. |
| * |
| * <-> |
| * 0123456789 |
| * VVVVNNN |
| * -> |
| * 0123456789 |
| * VNNNNNN |
| */ |
| vma = alloc_and_link_vma(&mm, 0x2000, 0x6000, 2, flags); |
| vma->vm_ops = &vm_ops; /* This should have no impact. */ |
| vma_next = alloc_and_link_vma(&mm, 0x6000, 0x9000, 6, flags); |
| vma_next->vm_ops = &vm_ops; /* This should have no impact. */ |
| vmg_set_range(&vmg, 0x3000, 0x6000, 3, flags); |
| vmg.vma = vma; |
| vmg.prev = vma; |
| vma->anon_vma = &dummy_anon_vma; |
| ASSERT_EQ(merge_existing(&vmg), vma_next); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_next->vm_start, 0x3000); |
| ASSERT_EQ(vma_next->vm_end, 0x9000); |
| ASSERT_EQ(vma_next->vm_pgoff, 3); |
| ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma); |
| ASSERT_EQ(vma->vm_start, 0x2000); |
| ASSERT_EQ(vma->vm_end, 0x3000); |
| ASSERT_EQ(vma->vm_pgoff, 2); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_TRUE(vma_write_started(vma_next)); |
| ASSERT_EQ(mm.map_count, 2); |
| |
| /* Clear down and reset. */ |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| /* |
| * Merge right case - full span. |
| * |
| * <--> |
| * 0123456789 |
| * VVVVNNN |
| * -> |
| * 0123456789 |
| * NNNNNNN |
| */ |
| vma = alloc_and_link_vma(&mm, 0x2000, 0x6000, 2, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x6000, 0x9000, 6, flags); |
| vma_next->vm_ops = &vm_ops; /* This should have no impact. */ |
| vmg_set_range(&vmg, 0x2000, 0x6000, 2, flags); |
| vmg.vma = vma; |
| vma->anon_vma = &dummy_anon_vma; |
| ASSERT_EQ(merge_existing(&vmg), vma_next); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_next->vm_start, 0x2000); |
| ASSERT_EQ(vma_next->vm_end, 0x9000); |
| ASSERT_EQ(vma_next->vm_pgoff, 2); |
| ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma_next)); |
| ASSERT_EQ(mm.map_count, 1); |
| |
| /* Clear down and reset. We should have deleted vma. */ |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 1); |
| |
| /* |
| * Merge left case - partial span. |
| * |
| * <-> |
| * 0123456789 |
| * PPPVVVV |
| * -> |
| * 0123456789 |
| * PPPPPPV |
| */ |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma_prev->vm_ops = &vm_ops; /* This should have no impact. */ |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x7000, 3, flags); |
| vma->vm_ops = &vm_ops; /* This should have no impact. */ |
| vmg_set_range(&vmg, 0x3000, 0x6000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| vma->anon_vma = &dummy_anon_vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x6000); |
| ASSERT_EQ(vma_prev->vm_pgoff, 0); |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_EQ(vma->vm_start, 0x6000); |
| ASSERT_EQ(vma->vm_end, 0x7000); |
| ASSERT_EQ(vma->vm_pgoff, 6); |
| ASSERT_TRUE(vma_write_started(vma_prev)); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 2); |
| |
| /* Clear down and reset. */ |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| /* |
| * Merge left case - full span. |
| * |
| * <--> |
| * 0123456789 |
| * PPPVVVV |
| * -> |
| * 0123456789 |
| * PPPPPPP |
| */ |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma_prev->vm_ops = &vm_ops; /* This should have no impact. */ |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x7000, 3, flags); |
| vmg_set_range(&vmg, 0x3000, 0x7000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| vma->anon_vma = &dummy_anon_vma; |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x7000); |
| ASSERT_EQ(vma_prev->vm_pgoff, 0); |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma_prev)); |
| ASSERT_EQ(mm.map_count, 1); |
| |
| /* Clear down and reset. We should have deleted vma. */ |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 1); |
| |
| /* |
| * Merge both case. |
| * |
| * <--> |
| * 0123456789 |
| * PPPVVVVNNN |
| * -> |
| * 0123456789 |
| * PPPPPPPPPP |
| */ |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma_prev->vm_ops = &vm_ops; /* This should have no impact. */ |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x7000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x7000, 0x9000, 7, flags); |
| vmg_set_range(&vmg, 0x3000, 0x7000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| vma->anon_vma = &dummy_anon_vma; |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x9000); |
| ASSERT_EQ(vma_prev->vm_pgoff, 0); |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_write_started(vma_prev)); |
| ASSERT_EQ(mm.map_count, 1); |
| |
| /* Clear down and reset. We should have deleted prev and next. */ |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 1); |
| |
| /* |
| * Non-merge ranges. the modified VMA merge operation assumes that the |
| * caller always specifies ranges within the input VMA so we need only |
| * examine these cases. |
| * |
| * - |
| * - |
| * - |
| * <-> |
| * <> |
| * <> |
| * 0123456789a |
| * PPPVVVVVNNN |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x8000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x8000, 0xa000, 8, flags); |
| |
| vmg_set_range(&vmg, 0x4000, 0x5000, 4, flags); |
| vmg.prev = vma; |
| vmg.vma = vma; |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| vmg_set_range(&vmg, 0x5000, 0x6000, 5, flags); |
| vmg.prev = vma; |
| vmg.vma = vma; |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| vmg_set_range(&vmg, 0x6000, 0x7000, 6, flags); |
| vmg.prev = vma; |
| vmg.vma = vma; |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| vmg_set_range(&vmg, 0x4000, 0x7000, 4, flags); |
| vmg.prev = vma; |
| vmg.vma = vma; |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| vmg_set_range(&vmg, 0x4000, 0x6000, 4, flags); |
| vmg.prev = vma; |
| vmg.vma = vma; |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| vmg_set_range(&vmg, 0x5000, 0x6000, 5, flags); |
| vmg.prev = vma; |
| vmg.vma = vma; |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_NOMERGE); |
| |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 3); |
| |
| return true; |
| } |
| |
| static bool test_anon_vma_non_mergeable(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vm_area_struct *vma, *vma_prev, *vma_next; |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| struct anon_vma_chain dummy_anon_vma_chain1 = { |
| .anon_vma = &dummy_anon_vma, |
| }; |
| struct anon_vma_chain dummy_anon_vma_chain2 = { |
| .anon_vma = &dummy_anon_vma, |
| }; |
| |
| /* |
| * In the case of modified VMA merge, merging both left and right VMAs |
| * but where prev and next have incompatible anon_vma objects, we revert |
| * to a merge of prev and VMA: |
| * |
| * <--> |
| * 0123456789 |
| * PPPVVVVNNN |
| * -> |
| * 0123456789 |
| * PPPPPPPNNN |
| */ |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x7000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x7000, 0x9000, 7, flags); |
| |
| /* |
| * Give both prev and next single anon_vma_chain fields, so they will |
| * merge with the NULL vmg->anon_vma. |
| * |
| * However, when prev is compared to next, the merge should fail. |
| */ |
| |
| INIT_LIST_HEAD(&vma_prev->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain1.same_vma, &vma_prev->anon_vma_chain); |
| ASSERT_TRUE(list_is_singular(&vma_prev->anon_vma_chain)); |
| vma_prev->anon_vma = &dummy_anon_vma; |
| ASSERT_TRUE(is_mergeable_anon_vma(NULL, vma_prev->anon_vma, vma_prev)); |
| |
| INIT_LIST_HEAD(&vma_next->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain2.same_vma, &vma_next->anon_vma_chain); |
| ASSERT_TRUE(list_is_singular(&vma_next->anon_vma_chain)); |
| vma_next->anon_vma = (struct anon_vma *)2; |
| ASSERT_TRUE(is_mergeable_anon_vma(NULL, vma_next->anon_vma, vma_next)); |
| |
| ASSERT_FALSE(is_mergeable_anon_vma(vma_prev->anon_vma, vma_next->anon_vma, NULL)); |
| |
| vmg_set_range(&vmg, 0x3000, 0x7000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x7000); |
| ASSERT_EQ(vma_prev->vm_pgoff, 0); |
| ASSERT_TRUE(vma_write_started(vma_prev)); |
| ASSERT_FALSE(vma_write_started(vma_next)); |
| |
| /* Clear down and reset. */ |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| /* |
| * Now consider the new VMA case. This is equivalent, only adding a new |
| * VMA in a gap between prev and next. |
| * |
| * <--> |
| * 0123456789 |
| * PPP****NNN |
| * -> |
| * 0123456789 |
| * PPPPPPPNNN |
| */ |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x7000, 0x9000, 7, flags); |
| |
| INIT_LIST_HEAD(&vma_prev->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain1.same_vma, &vma_prev->anon_vma_chain); |
| vma_prev->anon_vma = (struct anon_vma *)1; |
| |
| INIT_LIST_HEAD(&vma_next->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain2.same_vma, &vma_next->anon_vma_chain); |
| vma_next->anon_vma = (struct anon_vma *)2; |
| |
| vmg_set_range(&vmg, 0x3000, 0x7000, 3, flags); |
| vmg.prev = vma_prev; |
| |
| ASSERT_EQ(merge_new(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x7000); |
| ASSERT_EQ(vma_prev->vm_pgoff, 0); |
| ASSERT_TRUE(vma_write_started(vma_prev)); |
| ASSERT_FALSE(vma_write_started(vma_next)); |
| |
| /* Final cleanup. */ |
| ASSERT_EQ(cleanup_mm(&mm, &vmi), 2); |
| |
| return true; |
| } |
| |
| static bool test_dup_anon_vma(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| struct anon_vma_chain dummy_anon_vma_chain = { |
| .anon_vma = &dummy_anon_vma, |
| }; |
| struct vm_area_struct *vma_prev, *vma_next, *vma; |
| |
| reset_dummy_anon_vma(); |
| |
| /* |
| * Expanding a VMA delete the next one duplicates next's anon_vma and |
| * assigns it to the expanded VMA. |
| * |
| * This covers new VMA merging, as these operations amount to a VMA |
| * expand. |
| */ |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma_next->anon_vma = &dummy_anon_vma; |
| |
| vmg_set_range(&vmg, 0, 0x5000, 0, flags); |
| vmg.vma = vma_prev; |
| vmg.next = vma_next; |
| |
| ASSERT_EQ(expand_existing(&vmg), 0); |
| |
| /* Will have been cloned. */ |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_prev->anon_vma->was_cloned); |
| |
| /* Cleanup ready for next run. */ |
| cleanup_mm(&mm, &vmi); |
| |
| /* |
| * next has anon_vma, we assign to prev. |
| * |
| * |<----->| |
| * |-------*********-------| |
| * prev vma next |
| * extend delete delete |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x5000, 0x8000, 5, flags); |
| |
| /* Initialise avc so mergeability check passes. */ |
| INIT_LIST_HEAD(&vma_next->anon_vma_chain); |
| list_add(&dummy_anon_vma_chain.same_vma, &vma_next->anon_vma_chain); |
| |
| vma_next->anon_vma = &dummy_anon_vma; |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x8000); |
| |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_prev->anon_vma->was_cloned); |
| |
| cleanup_mm(&mm, &vmi); |
| |
| /* |
| * vma has anon_vma, we assign to prev. |
| * |
| * |<----->| |
| * |-------*********-------| |
| * prev vma next |
| * extend delete delete |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x5000, 0x8000, 5, flags); |
| |
| vma->anon_vma = &dummy_anon_vma; |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x8000); |
| |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_prev->anon_vma->was_cloned); |
| |
| cleanup_mm(&mm, &vmi); |
| |
| /* |
| * vma has anon_vma, we assign to prev. |
| * |
| * |<----->| |
| * |-------************* |
| * prev vma |
| * extend shrink/delete |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x8000, 3, flags); |
| |
| vma->anon_vma = &dummy_anon_vma; |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), vma_prev); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| |
| ASSERT_EQ(vma_prev->vm_start, 0); |
| ASSERT_EQ(vma_prev->vm_end, 0x5000); |
| |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_prev->anon_vma->was_cloned); |
| |
| cleanup_mm(&mm, &vmi); |
| |
| /* |
| * vma has anon_vma, we assign to next. |
| * |
| * |<----->| |
| * *************-------| |
| * vma next |
| * shrink/delete extend |
| */ |
| |
| vma = alloc_and_link_vma(&mm, 0, 0x5000, 0, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x5000, 0x8000, 5, flags); |
| |
| vma->anon_vma = &dummy_anon_vma; |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma; |
| vmg.vma = vma; |
| |
| ASSERT_EQ(merge_existing(&vmg), vma_next); |
| ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS); |
| |
| ASSERT_EQ(vma_next->vm_start, 0x3000); |
| ASSERT_EQ(vma_next->vm_end, 0x8000); |
| |
| ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(vma_next->anon_vma->was_cloned); |
| |
| cleanup_mm(&mm, &vmi); |
| return true; |
| } |
| |
| static bool test_vmi_prealloc_fail(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vma_merge_struct vmg = { |
| .mm = &mm, |
| .vmi = &vmi, |
| }; |
| struct vm_area_struct *vma_prev, *vma; |
| |
| /* |
| * We are merging vma into prev, with vma possessing an anon_vma, which |
| * will be duplicated. We cause the vmi preallocation to fail and assert |
| * the duplicated anon_vma is unlinked. |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma->anon_vma = &dummy_anon_vma; |
| |
| vmg_set_range(&vmg, 0x3000, 0x5000, 3, flags); |
| vmg.prev = vma_prev; |
| vmg.vma = vma; |
| |
| fail_prealloc = true; |
| |
| /* This will cause the merge to fail. */ |
| ASSERT_EQ(merge_existing(&vmg), NULL); |
| ASSERT_EQ(vmg.state, VMA_MERGE_ERROR_NOMEM); |
| /* We will already have assigned the anon_vma. */ |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| /* And it was both cloned and unlinked. */ |
| ASSERT_TRUE(dummy_anon_vma.was_cloned); |
| ASSERT_TRUE(dummy_anon_vma.was_unlinked); |
| |
| cleanup_mm(&mm, &vmi); /* Resets fail_prealloc too. */ |
| |
| /* |
| * We repeat the same operation for expanding a VMA, which is what new |
| * VMA merging ultimately uses too. This asserts that unlinking is |
| * performed in this case too. |
| */ |
| |
| vma_prev = alloc_and_link_vma(&mm, 0, 0x3000, 0, flags); |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma->anon_vma = &dummy_anon_vma; |
| |
| vmg_set_range(&vmg, 0, 0x5000, 3, flags); |
| vmg.vma = vma_prev; |
| vmg.next = vma; |
| |
| fail_prealloc = true; |
| ASSERT_EQ(expand_existing(&vmg), -ENOMEM); |
| ASSERT_EQ(vmg.state, VMA_MERGE_ERROR_NOMEM); |
| |
| ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma); |
| ASSERT_TRUE(dummy_anon_vma.was_cloned); |
| ASSERT_TRUE(dummy_anon_vma.was_unlinked); |
| |
| cleanup_mm(&mm, &vmi); |
| return true; |
| } |
| |
| static bool test_merge_extend(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| VMA_ITERATOR(vmi, &mm, 0x1000); |
| struct vm_area_struct *vma; |
| |
| vma = alloc_and_link_vma(&mm, 0, 0x1000, 0, flags); |
| alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, flags); |
| |
| /* |
| * Extend a VMA into the gap between itself and the following VMA. |
| * This should result in a merge. |
| * |
| * <-> |
| * * * |
| * |
| */ |
| |
| ASSERT_EQ(vma_merge_extend(&vmi, vma, 0x2000), vma); |
| ASSERT_EQ(vma->vm_start, 0); |
| ASSERT_EQ(vma->vm_end, 0x4000); |
| ASSERT_EQ(vma->vm_pgoff, 0); |
| ASSERT_TRUE(vma_write_started(vma)); |
| ASSERT_EQ(mm.map_count, 1); |
| |
| cleanup_mm(&mm, &vmi); |
| return true; |
| } |
| |
| static bool test_copy_vma(void) |
| { |
| unsigned long flags = VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE; |
| struct mm_struct mm = {}; |
| bool need_locks = false; |
| VMA_ITERATOR(vmi, &mm, 0); |
| struct vm_area_struct *vma, *vma_new, *vma_next; |
| |
| /* Move backwards and do not merge. */ |
| |
| vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, flags); |
| vma_new = copy_vma(&vma, 0, 0x2000, 0, &need_locks); |
| |
| ASSERT_NE(vma_new, vma); |
| ASSERT_EQ(vma_new->vm_start, 0); |
| ASSERT_EQ(vma_new->vm_end, 0x2000); |
| ASSERT_EQ(vma_new->vm_pgoff, 0); |
| |
| cleanup_mm(&mm, &vmi); |
| |
| /* Move a VMA into position next to another and merge the two. */ |
| |
| vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, flags); |
| vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, flags); |
| vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, &need_locks); |
| |
| ASSERT_EQ(vma_new, vma_next); |
| |
| cleanup_mm(&mm, &vmi); |
| return true; |
| } |
| |
| int main(void) |
| { |
| int num_tests = 0, num_fail = 0; |
| |
| maple_tree_init(); |
| |
| #define TEST(name) \ |
| do { \ |
| num_tests++; \ |
| if (!test_##name()) { \ |
| num_fail++; \ |
| fprintf(stderr, "Test " #name " FAILED\n"); \ |
| } \ |
| } while (0) |
| |
| /* Very simple tests to kick the tyres. */ |
| TEST(simple_merge); |
| TEST(simple_modify); |
| TEST(simple_expand); |
| TEST(simple_shrink); |
| |
| TEST(merge_new); |
| TEST(vma_merge_special_flags); |
| TEST(vma_merge_with_close); |
| TEST(vma_merge_new_with_close); |
| TEST(merge_existing); |
| TEST(anon_vma_non_mergeable); |
| TEST(dup_anon_vma); |
| TEST(vmi_prealloc_fail); |
| TEST(merge_extend); |
| TEST(copy_vma); |
| |
| #undef TEST |
| |
| printf("%d tests run, %d passed, %d failed.\n", |
| num_tests, num_tests - num_fail, num_fail); |
| |
| return num_fail == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |