Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Lorenzo Stoakes and committed by
Andrew Morton
742d3663 d5f416c7

+261 -3
+261 -3
tools/testing/selftests/mm/mremap_test.c
··· 5 5 #define _GNU_SOURCE 6 6 7 7 #include <errno.h> 8 + #include <fcntl.h> 9 + #include <linux/userfaultfd.h> 8 10 #include <stdlib.h> 9 11 #include <stdio.h> 10 12 #include <string.h> 13 + #include <sys/ioctl.h> 11 14 #include <sys/mman.h> 15 + #include <syscall.h> 12 16 #include <time.h> 13 17 #include <stdbool.h> 14 18 ··· 172 168 173 169 if (first_val <= start && second_val >= end) { 174 170 success = true; 171 + fflush(maps_fp); 175 172 break; 176 173 } 177 174 } 178 175 179 176 return success; 177 + } 178 + 179 + /* Check if [ptr, ptr + size) mapped in /proc/self/maps. */ 180 + static bool is_ptr_mapped(FILE *maps_fp, void *ptr, unsigned long size) 181 + { 182 + unsigned long start = (unsigned long)ptr; 183 + unsigned long end = start + size; 184 + 185 + return is_range_mapped(maps_fp, start, end); 180 186 } 181 187 182 188 /* ··· 747 733 dont_unmap ? " [dontunnmap]" : ""); 748 734 } 749 735 736 + #ifdef __NR_userfaultfd 737 + static void mremap_move_multi_invalid_vmas(FILE *maps_fp, 738 + unsigned long page_size) 739 + { 740 + char *test_name = "mremap move multiple invalid vmas"; 741 + const size_t size = 10 * page_size; 742 + bool success = true; 743 + char *ptr, *tgt_ptr; 744 + int uffd, err, i; 745 + void *res; 746 + struct uffdio_api api = { 747 + .api = UFFD_API, 748 + .features = UFFD_EVENT_PAGEFAULT, 749 + }; 750 + 751 + uffd = syscall(__NR_userfaultfd, O_NONBLOCK); 752 + if (uffd == -1) { 753 + err = errno; 754 + perror("userfaultfd"); 755 + if (err == EPERM) { 756 + ksft_test_result_skip("%s - missing uffd", test_name); 757 + return; 758 + } 759 + success = false; 760 + goto out; 761 + } 762 + if (ioctl(uffd, UFFDIO_API, &api)) { 763 + perror("ioctl UFFDIO_API"); 764 + success = false; 765 + goto out_close_uffd; 766 + } 767 + 768 + ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, 769 + MAP_PRIVATE | MAP_ANON, -1, 0); 770 + if (ptr == MAP_FAILED) { 771 + perror("mmap"); 772 + success = false; 773 + goto out_close_uffd; 774 + } 775 + 776 + tgt_ptr = mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); 777 + if (tgt_ptr == MAP_FAILED) { 778 + perror("mmap"); 779 + success = false; 780 + goto out_close_uffd; 781 + } 782 + if (munmap(tgt_ptr, size)) { 783 + perror("munmap"); 784 + success = false; 785 + goto out_unmap; 786 + } 787 + 788 + /* 789 + * Unmap so we end up with: 790 + * 791 + * 0 2 4 6 8 10 offset in buffer 792 + * |*| |*| |*| |*| |*| 793 + * |*| |*| |*| |*| |*| 794 + * 795 + * Additionally, register each with UFFD. 796 + */ 797 + for (i = 0; i < 10; i += 2) { 798 + void *unmap_ptr = &ptr[(i + 1) * page_size]; 799 + unsigned long start = (unsigned long)&ptr[i * page_size]; 800 + struct uffdio_register reg = { 801 + .range = { 802 + .start = start, 803 + .len = page_size, 804 + }, 805 + .mode = UFFDIO_REGISTER_MODE_MISSING, 806 + }; 807 + 808 + if (ioctl(uffd, UFFDIO_REGISTER, &reg) == -1) { 809 + perror("ioctl UFFDIO_REGISTER"); 810 + success = false; 811 + goto out_unmap; 812 + } 813 + if (munmap(unmap_ptr, page_size)) { 814 + perror("munmap"); 815 + success = false; 816 + goto out_unmap; 817 + } 818 + } 819 + 820 + /* 821 + * Now try to move the entire range which is invalid for multi VMA move. 822 + * 823 + * This will fail, and no VMA should be moved, as we check this ahead of 824 + * time. 825 + */ 826 + res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr); 827 + err = errno; 828 + if (res != MAP_FAILED) { 829 + fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n"); 830 + success = false; 831 + goto out_unmap; 832 + } 833 + if (err != EFAULT) { 834 + errno = err; 835 + perror("mrmeap() unexpected error"); 836 + success = false; 837 + goto out_unmap; 838 + } 839 + if (is_ptr_mapped(maps_fp, tgt_ptr, page_size)) { 840 + fprintf(stderr, 841 + "Invalid uffd-armed VMA at start of multi range moved\n"); 842 + success = false; 843 + goto out_unmap; 844 + } 845 + 846 + /* 847 + * Now try to move a single VMA, this should succeed as not multi VMA 848 + * move. 849 + */ 850 + res = mremap(ptr, page_size, page_size, 851 + MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr); 852 + if (res == MAP_FAILED) { 853 + perror("mremap single invalid-multi VMA"); 854 + success = false; 855 + goto out_unmap; 856 + } 857 + 858 + /* 859 + * Unmap the VMA, and remap a non-uffd registered (therefore, multi VMA 860 + * move valid) VMA at the start of ptr range. 861 + */ 862 + if (munmap(tgt_ptr, page_size)) { 863 + perror("munmap"); 864 + success = false; 865 + goto out_unmap; 866 + } 867 + res = mmap(ptr, page_size, PROT_READ | PROT_WRITE, 868 + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); 869 + if (res == MAP_FAILED) { 870 + perror("mmap"); 871 + success = false; 872 + goto out_unmap; 873 + } 874 + 875 + /* 876 + * Now try to move the entire range, we should succeed in moving the 877 + * first VMA, but no others, and report a failure. 878 + */ 879 + res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr); 880 + err = errno; 881 + if (res != MAP_FAILED) { 882 + fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n"); 883 + success = false; 884 + goto out_unmap; 885 + } 886 + if (err != EFAULT) { 887 + errno = err; 888 + perror("mrmeap() unexpected error"); 889 + success = false; 890 + goto out_unmap; 891 + } 892 + if (!is_ptr_mapped(maps_fp, tgt_ptr, page_size)) { 893 + fprintf(stderr, "Valid VMA not moved\n"); 894 + success = false; 895 + goto out_unmap; 896 + } 897 + 898 + /* 899 + * Unmap the VMA, and map valid VMA at start of ptr range, and replace 900 + * all existing multi-move invalid VMAs, except the last, with valid 901 + * multi-move VMAs. 902 + */ 903 + if (munmap(tgt_ptr, page_size)) { 904 + perror("munmap"); 905 + success = false; 906 + goto out_unmap; 907 + } 908 + if (munmap(ptr, size - 2 * page_size)) { 909 + perror("munmap"); 910 + success = false; 911 + goto out_unmap; 912 + } 913 + for (i = 0; i < 8; i += 2) { 914 + res = mmap(&ptr[i * page_size], page_size, 915 + PROT_READ | PROT_WRITE, 916 + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); 917 + if (res == MAP_FAILED) { 918 + perror("mmap"); 919 + success = false; 920 + goto out_unmap; 921 + } 922 + } 923 + 924 + /* 925 + * Now try to move the entire range, we should succeed in moving all but 926 + * the last VMA, and report a failure. 927 + */ 928 + res = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tgt_ptr); 929 + err = errno; 930 + if (res != MAP_FAILED) { 931 + fprintf(stderr, "mremap() succeeded for multi VMA uffd armed\n"); 932 + success = false; 933 + goto out_unmap; 934 + } 935 + if (err != EFAULT) { 936 + errno = err; 937 + perror("mrmeap() unexpected error"); 938 + success = false; 939 + goto out_unmap; 940 + } 941 + 942 + for (i = 0; i < 10; i += 2) { 943 + bool is_mapped = is_ptr_mapped(maps_fp, 944 + &tgt_ptr[i * page_size], page_size); 945 + 946 + if (i < 8 && !is_mapped) { 947 + fprintf(stderr, "Valid VMA not moved at %d\n", i); 948 + success = false; 949 + goto out_unmap; 950 + } else if (i == 8 && is_mapped) { 951 + fprintf(stderr, "Invalid VMA moved at %d\n", i); 952 + success = false; 953 + goto out_unmap; 954 + } 955 + } 956 + 957 + out_unmap: 958 + if (munmap(tgt_ptr, size)) 959 + perror("munmap tgt"); 960 + if (munmap(ptr, size)) 961 + perror("munmap src"); 962 + out_close_uffd: 963 + close(uffd); 964 + out: 965 + if (success) 966 + ksft_test_result_pass("%s\n", test_name); 967 + else 968 + ksft_test_result_fail("%s\n", test_name); 969 + } 970 + #else 971 + static void mremap_move_multi_invalid_vmas(FILE *maps_fp, unsigned long page_size) 972 + { 973 + char *test_name = "mremap move multiple invalid vmas"; 974 + 975 + ksft_test_result_skip("%s - missing uffd", test_name); 976 + } 977 + #endif /* __NR_userfaultfd */ 978 + 750 979 /* Returns the time taken for the remap on success else returns -1. */ 751 980 static long long remap_region(struct config c, unsigned int threshold_mb, 752 981 char *rand_addr) ··· 1331 1074 char *rand_addr; 1332 1075 size_t rand_size; 1333 1076 int num_expand_tests = 2; 1334 - int num_misc_tests = 8; 1077 + int num_misc_tests = 9; 1335 1078 struct test test_cases[MAX_TEST] = {}; 1336 1079 struct test perf_test_cases[MAX_PERF_TEST]; 1337 1080 int page_size; ··· 1454 1197 mremap_expand_merge(maps_fp, page_size); 1455 1198 mremap_expand_merge_offset(maps_fp, page_size); 1456 1199 1457 - fclose(maps_fp); 1458 - 1459 1200 mremap_move_within_range(pattern_seed, rand_addr); 1460 1201 mremap_move_1mb_from_start(pattern_seed, rand_addr); 1461 1202 mremap_shrink_multiple_vmas(page_size, /* inplace= */true); ··· 1462 1207 mremap_move_multiple_vmas(pattern_seed, page_size, /* dontunmap= */ true); 1463 1208 mremap_move_multiple_vmas_split(pattern_seed, page_size, /* dontunmap= */ false); 1464 1209 mremap_move_multiple_vmas_split(pattern_seed, page_size, /* dontunmap= */ true); 1210 + mremap_move_multi_invalid_vmas(maps_fp, page_size); 1211 + 1212 + fclose(maps_fp); 1465 1213 1466 1214 if (run_perf_tests) { 1467 1215 ksft_print_msg("\n%s\n",