// SPDX-License-Identifier: GPL-2.0-or-later #define _GNU_SOURCE #include "../kselftest_harness.h" #include #include #include #include #include #include #include #include #include #include #include "vm_util.h" #include FIXTURE(merge) { unsigned int page_size; char *carveout; struct procmap_fd procmap; }; FIXTURE_SETUP(merge) { self->page_size = psize(); /* Carve out PROT_NONE region to map over. */ self->carveout = mmap(NULL, 30 * self->page_size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); ASSERT_NE(self->carveout, MAP_FAILED); /* Setup PROCMAP_QUERY interface. */ ASSERT_EQ(open_self_procmap(&self->procmap), 0); } FIXTURE_TEARDOWN(merge) { ASSERT_EQ(munmap(self->carveout, 30 * self->page_size), 0); ASSERT_EQ(close_procmap(&self->procmap), 0); /* * Clear unconditionally, as some tests set this. It is no issue if this * fails (KSM may be disabled for instance). */ prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0); } TEST_F(merge, mprotect_unfaulted_left) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr; /* * Map 10 pages of R/W memory within. MAP_NORESERVE so we don't hit * merge failure due to lack of VM_ACCOUNT flag by mistake. * * |-----------------------| * | unfaulted | * |-----------------------| */ ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Now make the first 5 pages read-only, splitting the VMA: * * RO RW * |-----------|-----------| * | unfaulted | unfaulted | * |-----------|-----------| */ ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0); /* * Fault in the first of the last 5 pages so it gets an anon_vma and * thus the whole VMA becomes 'faulted': * * RO RW * |-----------|-----------| * | unfaulted | faulted | * |-----------|-----------| */ ptr[5 * page_size] = 'x'; /* * Now mprotect() the RW region read-only, we should merge (though for * ~15 years we did not! :): * * RO * |-----------------------| * | faulted | * |-----------------------| */ ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0); /* Assert that the merge succeeded using PROCMAP_QUERY. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); } TEST_F(merge, mprotect_unfaulted_right) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr; /* * |-----------------------| * | unfaulted | * |-----------------------| */ ptr = mmap(&carveout[page_size], 10 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Now make the last 5 pages read-only, splitting the VMA: * * RW RO * |-----------|-----------| * | unfaulted | unfaulted | * |-----------|-----------| */ ASSERT_EQ(mprotect(&ptr[5 * page_size], 5 * page_size, PROT_READ), 0); /* * Fault in the first of the first 5 pages so it gets an anon_vma and * thus the whole VMA becomes 'faulted': * * RW RO * |-----------|-----------| * | faulted | unfaulted | * |-----------|-----------| */ ptr[0] = 'x'; /* * Now mprotect() the RW region read-only, we should merge: * * RO * |-----------------------| * | faulted | * |-----------------------| */ ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ), 0); /* Assert that the merge succeeded using PROCMAP_QUERY. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); } TEST_F(merge, mprotect_unfaulted_both) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr; /* * |-----------------------| * | unfaulted | * |-----------------------| */ ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Now make the first and last 3 pages read-only, splitting the VMA: * * RO RW RO * |-----------|-----------|-----------| * | unfaulted | unfaulted | unfaulted | * |-----------|-----------|-----------| */ ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0); ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0); /* * Fault in the first of the middle 3 pages so it gets an anon_vma and * thus the whole VMA becomes 'faulted': * * RO RW RO * |-----------|-----------|-----------| * | unfaulted | faulted | unfaulted | * |-----------|-----------|-----------| */ ptr[3 * page_size] = 'x'; /* * Now mprotect() the RW region read-only, we should merge: * * RO * |-----------------------| * | faulted | * |-----------------------| */ ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0); /* Assert that the merge succeeded using PROCMAP_QUERY. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size); } TEST_F(merge, mprotect_faulted_left_unfaulted_right) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr; /* * |-----------------------| * | unfaulted | * |-----------------------| */ ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Now make the last 3 pages read-only, splitting the VMA: * * RW RO * |-----------------------|-----------| * | unfaulted | unfaulted | * |-----------------------|-----------| */ ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0); /* * Fault in the first of the first 6 pages so it gets an anon_vma and * thus the whole VMA becomes 'faulted': * * RW RO * |-----------------------|-----------| * | unfaulted | unfaulted | * |-----------------------|-----------| */ ptr[0] = 'x'; /* * Now make the first 3 pages read-only, splitting the VMA: * * RO RW RO * |-----------|-----------|-----------| * | faulted | faulted | unfaulted | * |-----------|-----------|-----------| */ ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0); /* * Now mprotect() the RW region read-only, we should merge: * * RO * |-----------------------| * | faulted | * |-----------------------| */ ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0); /* Assert that the merge succeeded using PROCMAP_QUERY. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size); } TEST_F(merge, mprotect_unfaulted_left_faulted_right) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr; /* * |-----------------------| * | unfaulted | * |-----------------------| */ ptr = mmap(&carveout[2 * page_size], 9 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Now make the first 3 pages read-only, splitting the VMA: * * RO RW * |-----------|-----------------------| * | unfaulted | unfaulted | * |-----------|-----------------------| */ ASSERT_EQ(mprotect(ptr, 3 * page_size, PROT_READ), 0); /* * Fault in the first of the last 6 pages so it gets an anon_vma and * thus the whole VMA becomes 'faulted': * * RO RW * |-----------|-----------------------| * | unfaulted | faulted | * |-----------|-----------------------| */ ptr[3 * page_size] = 'x'; /* * Now make the last 3 pages read-only, splitting the VMA: * * RO RW RO * |-----------|-----------|-----------| * | unfaulted | faulted | faulted | * |-----------|-----------|-----------| */ ASSERT_EQ(mprotect(&ptr[6 * page_size], 3 * page_size, PROT_READ), 0); /* * Now mprotect() the RW region read-only, we should merge: * * RO * |-----------------------| * | faulted | * |-----------------------| */ ASSERT_EQ(mprotect(&ptr[3 * page_size], 3 * page_size, PROT_READ), 0); /* Assert that the merge succeeded using PROCMAP_QUERY. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 9 * page_size); } TEST_F(merge, forked_target_vma) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; pid_t pid; char *ptr, *ptr2; int i; /* * |-----------| * | unfaulted | * |-----------| */ ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Fault in process. * * |-----------| * | faulted | * |-----------| */ ptr[0] = 'x'; pid = fork(); ASSERT_NE(pid, -1); if (pid != 0) { wait(NULL); return; } /* Child process below: */ /* Reopen for child. */ ASSERT_EQ(close_procmap(&self->procmap), 0); ASSERT_EQ(open_self_procmap(&self->procmap), 0); /* unCOWing everything does not cause the AVC to go away. */ for (i = 0; i < 5 * page_size; i += page_size) ptr[i] = 'x'; /* * Map in adjacent VMA in child. * * forked * |-----------|-----------| * | faulted | unfaulted | * |-----------|-----------| * ptr ptr2 */ ptr2 = mmap(&ptr[5 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); /* Make sure not merged. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 5 * page_size); } TEST_F(merge, forked_source_vma) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; pid_t pid; char *ptr, *ptr2; int i; /* * |-----------|------------| * | unfaulted | | * |-----------|------------| */ ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Fault in process. * * |-----------|------------| * | faulted | | * |-----------|------------| */ ptr[0] = 'x'; pid = fork(); ASSERT_NE(pid, -1); if (pid != 0) { wait(NULL); return; } /* Child process below: */ /* Reopen for child. */ ASSERT_EQ(close_procmap(&self->procmap), 0); ASSERT_EQ(open_self_procmap(&self->procmap), 0); /* unCOWing everything does not cause the AVC to go away. */ for (i = 0; i < 5 * page_size; i += page_size) ptr[i] = 'x'; /* * Map in adjacent VMA in child, ptr2 after ptr, but incompatible. * * forked RW RWX * |-----------|-----------| * | faulted | unfaulted | * |-----------|-----------| * ptr ptr2 */ ptr2 = mmap(&carveout[6 * page_size], 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); /* Make sure not merged. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr2)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size); /* * Now mprotect forked region to RWX so it becomes the source for the * merge to unfaulted region: * * forked RWX RWX * |-----------|-----------| * | faulted | unfaulted | * |-----------|-----------| * ptr ptr2 * * This should NOT result in a merge, as ptr was forked. */ ASSERT_EQ(mprotect(ptr, 5 * page_size, PROT_READ | PROT_WRITE | PROT_EXEC), 0); /* Again, make sure not merged. */ ASSERT_TRUE(find_vma_procmap(procmap, ptr2)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 5 * page_size); } TEST_F(merge, handle_uprobe_upon_merged_vma) { const size_t attr_sz = sizeof(struct perf_event_attr); unsigned int page_size = self->page_size; const char *probe_file = "./foo"; char *carveout = self->carveout; struct perf_event_attr attr; unsigned long type; void *ptr1, *ptr2; int fd; fd = open(probe_file, O_RDWR|O_CREAT, 0600); ASSERT_GE(fd, 0); ASSERT_EQ(ftruncate(fd, page_size), 0); if (read_sysfs("/sys/bus/event_source/devices/uprobe/type", &type) != 0) { SKIP(goto out, "Failed to read uprobe sysfs file, skipping"); } memset(&attr, 0, attr_sz); attr.size = attr_sz; attr.type = type; attr.config1 = (__u64)(long)probe_file; attr.config2 = 0x0; ASSERT_GE(syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0), 0); ptr1 = mmap(&carveout[page_size], 10 * page_size, PROT_EXEC, MAP_PRIVATE | MAP_FIXED, fd, 0); ASSERT_NE(ptr1, MAP_FAILED); ptr2 = mremap(ptr1, page_size, 2 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr1 + 5 * page_size); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_NE(mremap(ptr2, page_size, page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr1), MAP_FAILED); out: close(fd); remove(probe_file); } TEST_F(merge, ksm_merge) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr, *ptr2; int err; /* * Map two R/W immediately adjacent to one another, they should * trivially merge: * * |-----------|-----------| * | R/W | R/W | * |-----------|-----------| * ptr ptr2 */ ptr = mmap(&carveout[page_size], page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); ptr2 = mmap(&carveout[2 * page_size], page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 2 * page_size); /* Unmap the second half of this merged VMA. */ ASSERT_EQ(munmap(ptr2, page_size), 0); /* OK, now enable global KSM merge. We clear this on test teardown. */ err = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0); if (err == -1) { int errnum = errno; /* Only non-failure case... */ ASSERT_EQ(errnum, EINVAL); /* ...but indicates we should skip. */ SKIP(return, "KSM memory merging not supported, skipping."); } /* * Now map a VMA adjacent to the existing that was just made * VM_MERGEABLE, this should merge as well. */ ptr2 = mmap(&carveout[2 * page_size], page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 2 * page_size); /* Now this VMA altogether. */ ASSERT_EQ(munmap(ptr, 2 * page_size), 0); /* Try the same operation as before, asserting this also merges fine. */ ptr = mmap(&carveout[page_size], page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); ptr2 = mmap(&carveout[2 * page_size], page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 2 * page_size); } TEST_F(merge, mremap_unfaulted_to_faulted) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr, *ptr2; /* * Map two distinct areas: * * |-----------| |-----------| * | unfaulted | | unfaulted | * |-----------| |-----------| * ptr ptr2 */ ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); ptr2 = mmap(&carveout[7 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); /* Offset ptr2 further away. */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr2 + page_size * 1000); ASSERT_NE(ptr2, MAP_FAILED); /* * Fault in ptr: * \ * |-----------| / |-----------| * | faulted | \ | unfaulted | * |-----------| / |-----------| * ptr \ ptr2 */ ptr[0] = 'x'; /* * Now move ptr2 adjacent to ptr: * * |-----------|-----------| * | faulted | unfaulted | * |-----------|-----------| * ptr ptr2 * * It should merge: * * |----------------------| * | faulted | * |----------------------| * ptr */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[5 * page_size]); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); } TEST_F(merge, mremap_unfaulted_behind_faulted) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr, *ptr2; /* * Map two distinct areas: * * |-----------| |-----------| * | unfaulted | | unfaulted | * |-----------| |-----------| * ptr ptr2 */ ptr = mmap(&carveout[6 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); ptr2 = mmap(&carveout[14 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); /* Offset ptr2 further away. */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr2 + page_size * 1000); ASSERT_NE(ptr2, MAP_FAILED); /* * Fault in ptr: * \ * |-----------| / |-----------| * | faulted | \ | unfaulted | * |-----------| / |-----------| * ptr \ ptr2 */ ptr[0] = 'x'; /* * Now move ptr2 adjacent, but behind, ptr: * * |-----------|-----------| * | unfaulted | faulted | * |-----------|-----------| * ptr2 ptr * * It should merge: * * |----------------------| * | faulted | * |----------------------| * ptr2 */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &carveout[page_size]); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr2)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 10 * page_size); } TEST_F(merge, mremap_unfaulted_between_faulted) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr, *ptr2, *ptr3; /* * Map three distinct areas: * * |-----------| |-----------| |-----------| * | unfaulted | | unfaulted | | unfaulted | * |-----------| |-----------| |-----------| * ptr ptr2 ptr3 */ ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); ptr2 = mmap(&carveout[7 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); ptr3 = mmap(&carveout[14 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr3, MAP_FAILED); /* Offset ptr3 further away. */ ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr3 + page_size * 2000); ASSERT_NE(ptr3, MAP_FAILED); /* Offset ptr2 further away. */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr2 + page_size * 1000); ASSERT_NE(ptr2, MAP_FAILED); /* * Fault in ptr, ptr3: * \ \ * |-----------| / |-----------| / |-----------| * | faulted | \ | unfaulted | \ | faulted | * |-----------| / |-----------| / |-----------| * ptr \ ptr2 \ ptr3 */ ptr[0] = 'x'; ptr3[0] = 'x'; /* * Move ptr3 back into place, leaving a place for ptr2: * \ * |-----------| |-----------| / |-----------| * | faulted | | faulted | \ | unfaulted | * |-----------| |-----------| / |-----------| * ptr ptr3 \ ptr2 */ ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[10 * page_size]); ASSERT_NE(ptr3, MAP_FAILED); /* * Finally, move ptr2 into place: * * |-----------|-----------|-----------| * | faulted | unfaulted | faulted | * |-----------|-----------|-----------| * ptr ptr2 ptr3 * * It should merge, but only ptr, ptr2: * * |-----------------------|-----------| * | faulted | unfaulted | * |-----------------------|-----------| */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[5 * page_size]); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); ASSERT_TRUE(find_vma_procmap(procmap, ptr3)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr3); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr3 + 5 * page_size); } TEST_F(merge, mremap_unfaulted_between_faulted_unfaulted) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr, *ptr2, *ptr3; /* * Map three distinct areas: * * |-----------| |-----------| |-----------| * | unfaulted | | unfaulted | | unfaulted | * |-----------| |-----------| |-----------| * ptr ptr2 ptr3 */ ptr = mmap(&carveout[page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); ptr2 = mmap(&carveout[7 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); ptr3 = mmap(&carveout[14 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr3, MAP_FAILED); /* Offset ptr3 further away. */ ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr3 + page_size * 2000); ASSERT_NE(ptr3, MAP_FAILED); /* Offset ptr2 further away. */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr2 + page_size * 1000); ASSERT_NE(ptr2, MAP_FAILED); /* * Fault in ptr: * \ \ * |-----------| / |-----------| / |-----------| * | faulted | \ | unfaulted | \ | unfaulted | * |-----------| / |-----------| / |-----------| * ptr \ ptr2 \ ptr3 */ ptr[0] = 'x'; /* * Move ptr3 back into place, leaving a place for ptr2: * \ * |-----------| |-----------| / |-----------| * | faulted | | unfaulted | \ | unfaulted | * |-----------| |-----------| / |-----------| * ptr ptr3 \ ptr2 */ ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[10 * page_size]); ASSERT_NE(ptr3, MAP_FAILED); /* * Finally, move ptr2 into place: * * |-----------|-----------|-----------| * | faulted | unfaulted | unfaulted | * |-----------|-----------|-----------| * ptr ptr2 ptr3 * * It should merge: * * |-----------------------------------| * | faulted | * |-----------------------------------| */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[5 * page_size]); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size); } TEST_F(merge, mremap_unfaulted_between_correctly_placed_faulted) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr, *ptr2; /* * Map one larger area: * * |-----------------------------------| * | unfaulted | * |-----------------------------------| */ ptr = mmap(&carveout[page_size], 15 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Fault in ptr: * * |-----------------------------------| * | faulted | * |-----------------------------------| */ ptr[0] = 'x'; /* * Unmap middle: * * |-----------| |-----------| * | faulted | | faulted | * |-----------| |-----------| * * Now the faulted areas are compatible with each other (anon_vma the * same, vma->vm_pgoff equal to virtual page offset). */ ASSERT_EQ(munmap(&ptr[5 * page_size], 5 * page_size), 0); /* * Map a new area, ptr2: * \ * |-----------| |-----------| / |-----------| * | faulted | | faulted | \ | unfaulted | * |-----------| |-----------| / |-----------| * ptr \ ptr2 */ ptr2 = mmap(&carveout[20 * page_size], 5 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr2, MAP_FAILED); /* * Finally, move ptr2 into place: * * |-----------|-----------|-----------| * | faulted | unfaulted | faulted | * |-----------|-----------|-----------| * ptr ptr2 ptr3 * * It should merge: * * |-----------------------------------| * | faulted | * |-----------------------------------| */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[5 * page_size]); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size); } TEST_F(merge, mremap_correct_placed_faulted) { unsigned int page_size = self->page_size; char *carveout = self->carveout; struct procmap_fd *procmap = &self->procmap; char *ptr, *ptr2, *ptr3; /* * Map one larger area: * * |-----------------------------------| * | unfaulted | * |-----------------------------------| */ ptr = mmap(&carveout[page_size], 15 * page_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); ASSERT_NE(ptr, MAP_FAILED); /* * Fault in ptr: * * |-----------------------------------| * | faulted | * |-----------------------------------| */ ptr[0] = 'x'; /* * Offset the final and middle 5 pages further away: * \ \ * |-----------| / |-----------| / |-----------| * | faulted | \ | faulted | \ | faulted | * |-----------| / |-----------| / |-----------| * ptr \ ptr2 \ ptr3 */ ptr3 = &ptr[10 * page_size]; ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr3 + page_size * 2000); ASSERT_NE(ptr3, MAP_FAILED); ptr2 = &ptr[5 * page_size]; ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr2 + page_size * 1000); ASSERT_NE(ptr2, MAP_FAILED); /* * Move ptr2 into its correct place: * \ * |-----------|-----------| / |-----------| * | faulted | faulted | \ | faulted | * |-----------|-----------| / |-----------| * ptr ptr2 \ ptr3 * * It should merge: * \ * |-----------------------| / |-----------| * | faulted | \ | faulted | * |-----------------------| / |-----------| * ptr \ ptr3 */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[5 * page_size]); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); /* * Now move ptr out of place: * \ \ * |-----------| / |-----------| / |-----------| * | faulted | \ | faulted | \ | faulted | * |-----------| / |-----------| / |-----------| * ptr2 \ ptr \ ptr3 */ ptr = sys_mremap(ptr, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr + page_size * 1000); ASSERT_NE(ptr, MAP_FAILED); /* * Now move ptr back into place: * \ * |-----------|-----------| / |-----------| * | faulted | faulted | \ | faulted | * |-----------|-----------| / |-----------| * ptr ptr2 \ ptr3 * * It should merge: * \ * |-----------------------| / |-----------| * | faulted | \ | faulted | * |-----------------------| / |-----------| * ptr \ ptr3 */ ptr = sys_mremap(ptr, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &carveout[page_size]); ASSERT_NE(ptr, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 10 * page_size); /* * Now move ptr out of place again: * \ \ * |-----------| / |-----------| / |-----------| * | faulted | \ | faulted | \ | faulted | * |-----------| / |-----------| / |-----------| * ptr2 \ ptr \ ptr3 */ ptr = sys_mremap(ptr, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr + page_size * 1000); ASSERT_NE(ptr, MAP_FAILED); /* * Now move ptr3 back into place: * \ * |-----------|-----------| / |-----------| * | faulted | faulted | \ | faulted | * |-----------|-----------| / |-----------| * ptr2 ptr3 \ ptr * * It should merge: * \ * |-----------------------| / |-----------| * | faulted | \ | faulted | * |-----------------------| / |-----------| * ptr2 \ ptr */ ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr2[5 * page_size]); ASSERT_NE(ptr3, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr2)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr2); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr2 + 10 * page_size); /* * Now move ptr back into place: * * |-----------|-----------------------| * | faulted | faulted | * |-----------|-----------------------| * ptr ptr2 * * It should merge: * * |-----------------------------------| * | faulted | * |-----------------------------------| * ptr */ ptr = sys_mremap(ptr, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &carveout[page_size]); ASSERT_NE(ptr, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size); /* * Now move ptr2 out of the way: * \ * |-----------| |-----------| / |-----------| * | faulted | | faulted | \ | faulted | * |-----------| |-----------| / |-----------| * ptr ptr3 \ ptr2 */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr2 + page_size * 1000); ASSERT_NE(ptr2, MAP_FAILED); /* * Now move it back: * * |-----------|-----------|-----------| * | faulted | faulted | faulted | * |-----------|-----------|-----------| * ptr ptr2 ptr3 * * It should merge: * * |-----------------------------------| * | faulted | * |-----------------------------------| * ptr */ ptr2 = sys_mremap(ptr2, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[5 * page_size]); ASSERT_NE(ptr2, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size); /* * Move ptr3 out of place: * \ * |-----------------------| / |-----------| * | faulted | \ | faulted | * |-----------------------| / |-----------| * ptr \ ptr3 */ ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, ptr3 + page_size * 1000); ASSERT_NE(ptr3, MAP_FAILED); /* * Now move it back: * * |-----------|-----------|-----------| * | faulted | faulted | faulted | * |-----------|-----------|-----------| * ptr ptr2 ptr3 * * It should merge: * * |-----------------------------------| * | faulted | * |-----------------------------------| * ptr */ ptr3 = sys_mremap(ptr3, 5 * page_size, 5 * page_size, MREMAP_MAYMOVE | MREMAP_FIXED, &ptr[10 * page_size]); ASSERT_NE(ptr3, MAP_FAILED); ASSERT_TRUE(find_vma_procmap(procmap, ptr)); ASSERT_EQ(procmap->query.vma_start, (unsigned long)ptr); ASSERT_EQ(procmap->query.vma_end, (unsigned long)ptr + 15 * page_size); } TEST_HARNESS_MAIN