Skip to content

Commit 742d366

Browse files
ljskernelakpm00
authored andcommitted
selftests/mm: add test for invalid multi VMA operations
We can use UFFD to easily assert invalid multi VMA moves, so do so, asserting expected behaviour when VMAs invalid for a multi VMA operation are encountered. We assert both that such operations are not permitted, and that we do not even attempt to move the first VMA under these circumstances. We also assert that we can still move a single VMA regardless. We then assert that a partial failure can occur if the invalid VMA appears later in the range of multiple VMAs, both at the very next VMA, and also at the end of the range. As part of this change, we are using the is_range_valid() helper more aggressively. Therefore, fix a bug where stale buffered data would hang around on success, causing subsequent calls to is_range_valid() to potentially give invalid results. We simply have to fflush() the stream on success to resolve this issue. Link: https://lkml.kernel.org/r/c4fb86dd5ba37610583ad5fc0e0c2306ddf318b9.1754218667.git.lorenzo.stoakes@oracle.com Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com> Cc: David Hildenbrand <david@redhat.com> Cc: Jann Horn <jannh@google.com> Cc: Liam Howlett <liam.howlett@oracle.com> Cc: Michal Hocko <mhocko@suse.com> Cc: Mike Rapoport <rppt@kernel.org> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Vlastimil Babka <vbabka@suse.cz> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent d5f416c commit 742d366

1 file changed

Lines changed: 261 additions & 3 deletions

File tree

tools/testing/selftests/mm/mremap_test.c

Lines changed: 261 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
#define _GNU_SOURCE
66

77
#include <errno.h>
8+
#include <fcntl.h>
9+
#include <linux/userfaultfd.h>
810
#include <stdlib.h>
911
#include <stdio.h>
1012
#include <string.h>
13+
#include <sys/ioctl.h>
1114
#include <sys/mman.h>
15+
#include <syscall.h>
1216
#include <time.h>
1317
#include <stdbool.h>
1418

@@ -168,13 +172,23 @@ static bool is_range_mapped(FILE *maps_fp, unsigned long start,
168172

169173
if (first_val <= start && second_val >= end) {
170174
success = true;
175+
fflush(maps_fp);
171176
break;
172177
}
173178
}
174179

175180
return success;
176181
}
177182

183+
/* Check if [ptr, ptr + size) mapped in /proc/self/maps. */
184+
static bool is_ptr_mapped(FILE *maps_fp, void *ptr, unsigned long size)
185+
{
186+
unsigned long start = (unsigned long)ptr;
187+
unsigned long end = start + size;
188+
189+
return is_range_mapped(maps_fp, start, end);
190+
}
191+
178192
/*
179193
* Returns the start address of the mapping on success, else returns
180194
* NULL on failure.
@@ -733,6 +747,249 @@ static void mremap_move_multiple_vmas_split(unsigned int pattern_seed,
733747
dont_unmap ? " [dontunnmap]" : "");
734748
}
735749

750+
#ifdef __NR_userfaultfd
751+
static void mremap_move_multi_invalid_vmas(FILE *maps_fp,
752+
unsigned long page_size)
753+
{
754+
char *test_name = "mremap move multiple invalid vmas";
755+
const size_t size = 10 * page_size;
756+
bool success = true;
757+
char *ptr, *tgt_ptr;
758+
int uffd, err, i;
759+
void *res;
760+
struct uffdio_api api = {
761+
.api = UFFD_API,
762+
.features = UFFD_EVENT_PAGEFAULT,
763+
};
764+
765+
uffd = syscall(__NR_userfaultfd, O_NONBLOCK);
766+
if (uffd == -1) {
767+
err = errno;
768+
perror("userfaultfd");
769+
if (err == EPERM) {
770+
ksft_test_result_skip("%s - missing uffd", test_name);
771+
return;
772+
}
773+
success = false;
774+
goto out;
775+
}
776+
if (ioctl(uffd, UFFDIO_API, &api)) {
777+
perror("ioctl UFFDIO_API");
778+
success = false;
779+
goto out_close_uffd;
780+
}
781+
782+
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
783+
MAP_PRIVATE | MAP_ANON, -1, 0);
784+
if (ptr == MAP_FAILED) {
785+
perror("mmap");
786+
success = false;
787+
goto out_close_uffd;
788+
}
789+
790+
tgt_ptr = mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
791+
if (tgt_ptr == MAP_FAILED) {
792+
perror("mmap");
793+
success = false;
794+
goto out_close_uffd;
795+
}
796+
if (munmap(tgt_ptr, size)) {
797+
perror("munmap");
798+
success = false;
799+
goto out_unmap;
800+
}
801+
802+
/*
803+
* Unmap so we end up with:
804+
*
805+
* 0 2 4 6 8 10 offset in buffer
806+
* |*| |*| |*| |*| |*|
807+
* |*| |*| |*| |*| |*|
808+
*
809+
* Additionally, register each with UFFD.
810+
*/
811+
for (i = 0; i < 10; i += 2) {
812+
void *unmap_ptr = &ptr[(i + 1) * page_size];
813+
unsigned long start = (unsigned long)&ptr[i * page_size];
814+
struct uffdio_register reg = {
815+
.range = {
816+
.start = start,
817+
.len = page_size,
818+
},
819+
.mode = UFFDIO_REGISTER_MODE_MISSING,
820+
};
821+
822+
if (ioctl(uffd, UFFDIO_REGISTER, &reg) == -1) {
823+
perror("ioctl UFFDIO_REGISTER");
824+
success = false;
825+
goto out_unmap;
826+
}
827+
if (munmap(unmap_ptr, page_size)) {
828+
perror("munmap");
829+
success = false;
830+
goto out_unmap;
831+
}
832+
}
833+
834+
/*
835+
* Now try to move the entire range which is invalid for multi VMA move.
836+
*
837+
* This will fail, and no VMA should be moved, as we check this ahead of
838+
* time.
839+
*/
840+
res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr);
841+
err = errno;
842+
if (res != MAP_FAILED) {
843+
fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n");
844+
success = false;
845+
goto out_unmap;
846+
}
847+
if (err != EFAULT) {
848+
errno = err;
849+
perror("mrmeap() unexpected error");
850+
success = false;
851+
goto out_unmap;
852+
}
853+
if (is_ptr_mapped(maps_fp, tgt_ptr, page_size)) {
854+
fprintf(stderr,
855+
"Invalid uffd-armed VMA at start of multi range moved\n");
856+
success = false;
857+
goto out_unmap;
858+
}
859+
860+
/*
861+
* Now try to move a single VMA, this should succeed as not multi VMA
862+
* move.
863+
*/
864+
res = mremap(ptr, page_size, page_size,
865+
MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr);
866+
if (res == MAP_FAILED) {
867+
perror("mremap single invalid-multi VMA");
868+
success = false;
869+
goto out_unmap;
870+
}
871+
872+
/*
873+
* Unmap the VMA, and remap a non-uffd registered (therefore, multi VMA
874+
* move valid) VMA at the start of ptr range.
875+
*/
876+
if (munmap(tgt_ptr, page_size)) {
877+
perror("munmap");
878+
success = false;
879+
goto out_unmap;
880+
}
881+
res = mmap(ptr, page_size, PROT_READ | PROT_WRITE,
882+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
883+
if (res == MAP_FAILED) {
884+
perror("mmap");
885+
success = false;
886+
goto out_unmap;
887+
}
888+
889+
/*
890+
* Now try to move the entire range, we should succeed in moving the
891+
* first VMA, but no others, and report a failure.
892+
*/
893+
res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr);
894+
err = errno;
895+
if (res != MAP_FAILED) {
896+
fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n");
897+
success = false;
898+
goto out_unmap;
899+
}
900+
if (err != EFAULT) {
901+
errno = err;
902+
perror("mrmeap() unexpected error");
903+
success = false;
904+
goto out_unmap;
905+
}
906+
if (!is_ptr_mapped(maps_fp, tgt_ptr, page_size)) {
907+
fprintf(stderr, "Valid VMA not moved\n");
908+
success = false;
909+
goto out_unmap;
910+
}
911+
912+
/*
913+
* Unmap the VMA, and map valid VMA at start of ptr range, and replace
914+
* all existing multi-move invalid VMAs, except the last, with valid
915+
* multi-move VMAs.
916+
*/
917+
if (munmap(tgt_ptr, page_size)) {
918+
perror("munmap");
919+
success = false;
920+
goto out_unmap;
921+
}
922+
if (munmap(ptr, size - 2 * page_size)) {
923+
perror("munmap");
924+
success = false;
925+
goto out_unmap;
926+
}
927+
for (i = 0; i < 8; i += 2) {
928+
res = mmap(&ptr[i * page_size], page_size,
929+
PROT_READ | PROT_WRITE,
930+
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
931+
if (res == MAP_FAILED) {
932+
perror("mmap");
933+
success = false;
934+
goto out_unmap;
935+
}
936+
}
937+
938+
/*
939+
* Now try to move the entire range, we should succeed in moving all but
940+
* the last VMA, and report a failure.
941+
*/
942+
res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr);
943+
err = errno;
944+
if (res != MAP_FAILED) {
945+
fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n");
946+
success = false;
947+
goto out_unmap;
948+
}
949+
if (err != EFAULT) {
950+
errno = err;
951+
perror("mrmeap() unexpected error");
952+
success = false;
953+
goto out_unmap;
954+
}
955+
956+
for (i = 0; i < 10; i += 2) {
957+
bool is_mapped = is_ptr_mapped(maps_fp,
958+
&tgt_ptr[i * page_size], page_size);
959+
960+
if (i < 8 && !is_mapped) {
961+
fprintf(stderr, "Valid VMA not moved at %d\n", i);
962+
success = false;
963+
goto out_unmap;
964+
} else if (i == 8 && is_mapped) {
965+
fprintf(stderr, "Invalid VMA moved at %d\n", i);
966+
success = false;
967+
goto out_unmap;
968+
}
969+
}
970+
971+
out_unmap:
972+
if (munmap(tgt_ptr, size))
973+
perror("munmap tgt");
974+
if (munmap(ptr, size))
975+
perror("munmap src");
976+
out_close_uffd:
977+
close(uffd);
978+
out:
979+
if (success)
980+
ksft_test_result_pass("%s\n", test_name);
981+
else
982+
ksft_test_result_fail("%s\n", test_name);
983+
}
984+
#else
985+
static void mremap_move_multi_invalid_vmas(FILE *maps_fp, unsigned long page_size)
986+
{
987+
char *test_name = "mremap move multiple invalid vmas";
988+
989+
ksft_test_result_skip("%s - missing uffd", test_name);
990+
}
991+
#endif /* __NR_userfaultfd */
992+
736993
/* Returns the time taken for the remap on success else returns -1. */
737994
static long long remap_region(struct config c, unsigned int threshold_mb,
738995
char *rand_addr)
@@ -1074,7 +1331,7 @@ int main(int argc, char **argv)
10741331
char *rand_addr;
10751332
size_t rand_size;
10761333
int num_expand_tests = 2;
1077-
int num_misc_tests = 8;
1334+
int num_misc_tests = 9;
10781335
struct test test_cases[MAX_TEST] = {};
10791336
struct test perf_test_cases[MAX_PERF_TEST];
10801337
int page_size;
@@ -1197,8 +1454,6 @@ int main(int argc, char **argv)
11971454
mremap_expand_merge(maps_fp, page_size);
11981455
mremap_expand_merge_offset(maps_fp, page_size);
11991456

1200-
fclose(maps_fp);
1201-
12021457
mremap_move_within_range(pattern_seed, rand_addr);
12031458
mremap_move_1mb_from_start(pattern_seed, rand_addr);
12041459
mremap_shrink_multiple_vmas(page_size, /* inplace= */true);
@@ -1207,6 +1462,9 @@ int main(int argc, char **argv)
12071462
mremap_move_multiple_vmas(pattern_seed, page_size, /* dontunmap= */ true);
12081463
mremap_move_multiple_vmas_split(pattern_seed, page_size, /* dontunmap= */ false);
12091464
mremap_move_multiple_vmas_split(pattern_seed, page_size, /* dontunmap= */ true);
1465+
mremap_move_multi_invalid_vmas(maps_fp, page_size);
1466+
1467+
fclose(maps_fp);
12101468

12111469
if (run_perf_tests) {
12121470
ksft_print_msg("\n%s\n",

0 commit comments

Comments
 (0)