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

userfaultfd: selftests: add write-protect test

Add uffd tests for write protection.

Instead of introducing new tests for it, let's simply squashing uffd-wp
tests into existing uffd-missing test cases. Changes are:

(1) Bouncing tests

We do the write-protection in two ways during the bouncing test:

- By using UFFDIO_COPY_MODE_WP when resolving MISSING pages: then
we'll make sure for each bounce process every single page will be
at least fault twice: once for MISSING, once for WP.

- By direct call UFFDIO_WRITEPROTECT on existing faulted memories:
To further torture the explicit page protection procedures of
uffd-wp, we split each bounce procedure into two halves (in the
background thread): the first half will be MISSING+WP for each
page as explained above. After the first half, we write protect
the faulted region in the background thread to make sure at least
half of the pages will be write protected again which is the first
half to test the new UFFDIO_WRITEPROTECT call. Then we continue
with the 2nd half, which will contain both MISSING and WP faulting
tests for the 2nd half and WP-only faults from the 1st half.

(2) Event/Signal test

Mostly previous tests but will do MISSING+WP for each page. For
sigbus-mode test we'll need to provide standalone path to handle the
write protection faults.

For all tests, do statistics as well for uffd-wp pages.

Signed-off-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Bobby Powers <bobbypowers@gmail.com>
Cc: Brian Geffon <bgeffon@google.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Denis Plotnikov <dplotnikov@virtuozzo.com>
Cc: "Dr . David Alan Gilbert" <dgilbert@redhat.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Jerome Glisse <jglisse@redhat.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: "Kirill A . Shutemov" <kirill@shutemov.name>
Cc: Martin Cracauer <cracauer@cons.org>
Cc: Marty McFadden <mcfadden8@llnl.gov>
Cc: Maya Gokhale <gokhale2@llnl.gov>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Mike Rapoport <rppt@linux.vnet.ibm.com>
Cc: Pavel Emelyanov <xemul@openvz.org>
Cc: Rik van Riel <riel@redhat.com>
Cc: Shaohua Li <shli@fb.com>
Link: http://lkml.kernel.org/r/20200220163112.11409-20-peterx@redhat.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Peter Xu and committed by
Linus Torvalds
9b12488a 5c8aed6c

+133 -24
+133 -24
tools/testing/selftests/vm/userfaultfd.c
··· 54 54 #include <linux/userfaultfd.h> 55 55 #include <setjmp.h> 56 56 #include <stdbool.h> 57 + #include <assert.h> 57 58 58 59 #include "../kselftest.h" 59 60 ··· 77 76 #define ALARM_INTERVAL_SECS 10 78 77 static volatile bool test_uffdio_copy_eexist = true; 79 78 static volatile bool test_uffdio_zeropage_eexist = true; 79 + /* Whether to test uffd write-protection */ 80 + static bool test_uffdio_wp = false; 80 81 81 82 static bool map_shared; 82 83 static int huge_fd; ··· 93 90 struct uffd_stats { 94 91 int cpu; 95 92 unsigned long missing_faults; 93 + unsigned long wp_faults; 96 94 }; 97 95 98 96 /* pthread_mutex_t starts at page offset 0 */ ··· 143 139 for (i = 0; i < n_cpus; i++) { 144 140 uffd_stats[i].cpu = i; 145 141 uffd_stats[i].missing_faults = 0; 142 + uffd_stats[i].wp_faults = 0; 146 143 } 144 + } 145 + 146 + static void uffd_stats_report(struct uffd_stats *stats, int n_cpus) 147 + { 148 + int i; 149 + unsigned long long miss_total = 0, wp_total = 0; 150 + 151 + for (i = 0; i < n_cpus; i++) { 152 + miss_total += stats[i].missing_faults; 153 + wp_total += stats[i].wp_faults; 154 + } 155 + 156 + printf("userfaults: %llu missing (", miss_total); 157 + for (i = 0; i < n_cpus; i++) 158 + printf("%lu+", stats[i].missing_faults); 159 + printf("\b), %llu wp (", wp_total); 160 + for (i = 0; i < n_cpus; i++) 161 + printf("%lu+", stats[i].wp_faults); 162 + printf("\b)\n"); 147 163 } 148 164 149 165 static int anon_release_pages(char *rel_area) ··· 286 262 void (*alias_mapping)(__u64 *start, size_t len, unsigned long offset); 287 263 }; 288 264 289 - #define ANON_EXPECTED_IOCTLS ((1 << _UFFDIO_WAKE) | \ 265 + #define SHMEM_EXPECTED_IOCTLS ((1 << _UFFDIO_WAKE) | \ 290 266 (1 << _UFFDIO_COPY) | \ 291 267 (1 << _UFFDIO_ZEROPAGE)) 268 + 269 + #define ANON_EXPECTED_IOCTLS ((1 << _UFFDIO_WAKE) | \ 270 + (1 << _UFFDIO_COPY) | \ 271 + (1 << _UFFDIO_ZEROPAGE) | \ 272 + (1 << _UFFDIO_WRITEPROTECT)) 292 273 293 274 static struct uffd_test_ops anon_uffd_test_ops = { 294 275 .expected_ioctls = ANON_EXPECTED_IOCTLS, ··· 303 274 }; 304 275 305 276 static struct uffd_test_ops shmem_uffd_test_ops = { 306 - .expected_ioctls = ANON_EXPECTED_IOCTLS, 277 + .expected_ioctls = SHMEM_EXPECTED_IOCTLS, 307 278 .allocate_area = shmem_allocate_area, 308 279 .release_pages = shmem_release_pages, 309 280 .alias_mapping = noop_alias_mapping, ··· 325 296 if (str1[i] != str2[i]) 326 297 return 1; 327 298 return 0; 299 + } 300 + 301 + static void wp_range(int ufd, __u64 start, __u64 len, bool wp) 302 + { 303 + struct uffdio_writeprotect prms = { 0 }; 304 + 305 + /* Write protection page faults */ 306 + prms.range.start = start; 307 + prms.range.len = len; 308 + /* Undo write-protect, do wakeup after that */ 309 + prms.mode = wp ? UFFDIO_WRITEPROTECT_MODE_WP : 0; 310 + 311 + if (ioctl(ufd, UFFDIO_WRITEPROTECT, &prms)) 312 + fprintf(stderr, "clear WP failed for address 0x%Lx\n", 313 + start), exit(1); 328 314 } 329 315 330 316 static void *locking_thread(void *arg) ··· 480 436 uffdio_copy.dst = (unsigned long) area_dst + offset; 481 437 uffdio_copy.src = (unsigned long) area_src + offset; 482 438 uffdio_copy.len = page_size; 483 - uffdio_copy.mode = 0; 439 + if (test_uffdio_wp) 440 + uffdio_copy.mode = UFFDIO_COPY_MODE_WP; 441 + else 442 + uffdio_copy.mode = 0; 484 443 uffdio_copy.copy = 0; 485 444 if (ioctl(ufd, UFFDIO_COPY, &uffdio_copy)) { 486 445 /* real retval in ufdio_copy.copy */ ··· 540 493 fprintf(stderr, "unexpected msg event %u\n", 541 494 msg->event), exit(1); 542 495 543 - if (bounces & BOUNCE_VERIFY && 544 - msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE) 545 - fprintf(stderr, "unexpected write fault\n"), exit(1); 496 + if (msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) { 497 + wp_range(uffd, msg->arg.pagefault.address, page_size, false); 498 + stats->wp_faults++; 499 + } else { 500 + /* Missing page faults */ 501 + if (bounces & BOUNCE_VERIFY && 502 + msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE) 503 + fprintf(stderr, "unexpected write fault\n"), exit(1); 546 504 547 - offset = (char *)(unsigned long)msg->arg.pagefault.address - area_dst; 548 - offset &= ~(page_size-1); 505 + offset = (char *)(unsigned long)msg->arg.pagefault.address - area_dst; 506 + offset &= ~(page_size-1); 549 507 550 - if (copy_page(uffd, offset)) 551 - stats->missing_faults++; 508 + if (copy_page(uffd, offset)) 509 + stats->missing_faults++; 510 + } 552 511 } 553 512 554 513 static void *uffd_poll_thread(void *arg) ··· 640 587 static void *background_thread(void *arg) 641 588 { 642 589 unsigned long cpu = (unsigned long) arg; 643 - unsigned long page_nr; 590 + unsigned long page_nr, start_nr, mid_nr, end_nr; 644 591 645 - for (page_nr = cpu * nr_pages_per_cpu; 646 - page_nr < (cpu+1) * nr_pages_per_cpu; 647 - page_nr++) 592 + start_nr = cpu * nr_pages_per_cpu; 593 + end_nr = (cpu+1) * nr_pages_per_cpu; 594 + mid_nr = (start_nr + end_nr) / 2; 595 + 596 + /* Copy the first half of the pages */ 597 + for (page_nr = start_nr; page_nr < mid_nr; page_nr++) 598 + copy_page_retry(uffd, page_nr * page_size); 599 + 600 + /* 601 + * If we need to test uffd-wp, set it up now. Then we'll have 602 + * at least the first half of the pages mapped already which 603 + * can be write-protected for testing 604 + */ 605 + if (test_uffdio_wp) 606 + wp_range(uffd, (unsigned long)area_dst + start_nr * page_size, 607 + nr_pages_per_cpu * page_size, true); 608 + 609 + /* 610 + * Continue the 2nd half of the page copying, handling write 611 + * protection faults if any 612 + */ 613 + for (page_nr = mid_nr; page_nr < end_nr; page_nr++) 648 614 copy_page_retry(uffd, page_nr * page_size); 649 615 650 616 return NULL; ··· 825 753 } 826 754 827 755 for (nr = 0; nr < split_nr_pages; nr++) { 756 + int steps = 1; 757 + unsigned long offset = nr * page_size; 758 + 828 759 if (signal_test) { 829 760 if (sigsetjmp(*sigbuf, 1) != 0) { 830 - if (nr == lastnr) { 761 + if (steps == 1 && nr == lastnr) { 831 762 fprintf(stderr, "Signal repeated\n"); 832 763 return 1; 833 764 } 834 765 835 766 lastnr = nr; 836 767 if (signal_test == 1) { 837 - if (copy_page(uffd, nr * page_size)) 838 - signalled++; 768 + if (steps == 1) { 769 + /* This is a MISSING request */ 770 + steps++; 771 + if (copy_page(uffd, offset)) 772 + signalled++; 773 + } else { 774 + /* This is a WP request */ 775 + assert(steps == 2); 776 + wp_range(uffd, 777 + (__u64)area_dst + 778 + offset, 779 + page_size, false); 780 + } 839 781 } else { 840 782 signalled++; 841 783 continue; ··· 862 776 fprintf(stderr, 863 777 "nr %lu memory corruption %Lu %Lu\n", 864 778 nr, count, 865 - count_verify[nr]), exit(1); 866 - } 779 + count_verify[nr]); 780 + } 781 + /* 782 + * Trigger write protection if there is by writting 783 + * the same value back. 784 + */ 785 + *area_count(area_dst, nr) = count; 867 786 } 868 787 869 788 if (signal_test) ··· 890 799 nr, count, 891 800 count_verify[nr]), exit(1); 892 801 } 802 + /* 803 + * Trigger write protection if there is by writting 804 + * the same value back. 805 + */ 806 + *area_count(area_dst, nr) = count; 893 807 } 894 808 895 809 if (uffd_test_ops->release_pages(area_dst)) ··· 998 902 uffdio_register.range.start = (unsigned long) area_dst; 999 903 uffdio_register.range.len = nr_pages * page_size; 1000 904 uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; 905 + if (test_uffdio_wp) 906 + uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; 1001 907 if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) 1002 908 fprintf(stderr, "register failure\n"), exit(1); 1003 909 ··· 1045 947 uffdio_register.range.start = (unsigned long) area_dst; 1046 948 uffdio_register.range.len = nr_pages * page_size; 1047 949 uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; 950 + if (test_uffdio_wp) 951 + uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; 1048 952 if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) 1049 953 fprintf(stderr, "register failure\n"), exit(1); 1050 954 ··· 1077 977 return 1; 1078 978 1079 979 close(uffd); 1080 - printf("userfaults: %ld\n", stats.missing_faults); 980 + 981 + uffd_stats_report(&stats, 1); 1081 982 1082 983 return stats.missing_faults != nr_pages; 1083 984 } ··· 1108 1007 uffdio_register.range.start = (unsigned long) area_dst; 1109 1008 uffdio_register.range.len = nr_pages * page_size; 1110 1009 uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; 1010 + if (test_uffdio_wp) 1011 + uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; 1111 1012 if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) 1112 1013 fprintf(stderr, "register failure\n"), exit(1); 1113 1014 ··· 1242 1139 uffdio_register.range.start = (unsigned long) area_dst; 1243 1140 uffdio_register.range.len = nr_pages * page_size; 1244 1141 uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; 1142 + if (test_uffdio_wp) 1143 + uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; 1245 1144 if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) { 1246 1145 fprintf(stderr, "register failure\n"); 1247 1146 return 1; ··· 1298 1193 if (stress(uffd_stats)) 1299 1194 return 1; 1300 1195 1196 + /* Clear all the write protections if there is any */ 1197 + if (test_uffdio_wp) 1198 + wp_range(uffd, (unsigned long)area_dst, 1199 + nr_pages * page_size, false); 1200 + 1301 1201 /* unregister */ 1302 1202 if (ioctl(uffd, UFFDIO_UNREGISTER, &uffdio_register.range)) { 1303 1203 fprintf(stderr, "unregister failure\n"); ··· 1341 1231 area_src_alias = area_dst_alias; 1342 1232 area_dst_alias = tmp_area; 1343 1233 1344 - printf("userfaults:"); 1345 - for (cpu = 0; cpu < nr_cpus; cpu++) 1346 - printf(" %lu", uffd_stats[cpu].missing_faults); 1347 - printf("\n"); 1234 + uffd_stats_report(uffd_stats, nr_cpus); 1348 1235 } 1349 1236 1350 1237 if (err) ··· 1381 1274 if (!strcmp(type, "anon")) { 1382 1275 test_type = TEST_ANON; 1383 1276 uffd_test_ops = &anon_uffd_test_ops; 1277 + /* Only enable write-protect test for anonymous test */ 1278 + test_uffdio_wp = true; 1384 1279 } else if (!strcmp(type, "hugetlb")) { 1385 1280 test_type = TEST_HUGETLB; 1386 1281 uffd_test_ops = &hugetlb_uffd_test_ops;