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. */
737994static 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