userfaultfd: selftests: fix SIGSEGV if huge mmap fails
authorAxel Rasmussen <axelrasmussen@google.com>
Sun, 6 Dec 2020 06:15:05 +0000 (22:15 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 6 Dec 2020 18:19:07 +0000 (10:19 -0800)
The error handling in hugetlb_allocate_area() was incorrect for the
hugetlb_shared test case.

Previously the behavior was:

- mmap a hugetlb area
  - If this fails, set the pointer to NULL, and carry on
- mmap an alias of the same hugetlb fd
  - If this fails, munmap the original area

If the original mmap failed, it's likely the second one did too.  If
both failed, we'd blindly try to munmap a NULL pointer, causing a
SIGSEGV.  Instead, "goto fail" so we return before trying to mmap the
alias.

This issue can be hit "in real life" by forgetting to set
/proc/sys/vm/nr_hugepages (leaving it at 0), and then trying to run the
hugetlb_shared test.

Another small improvement is, when the original mmap fails, don't just
print "it failed": perror(), so we can see *why*.  :)

Signed-off-by: Axel Rasmussen <axelrasmussen@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Peter Xu <peterx@redhat.com>
Cc: Joe Perches <joe@perches.com>
Cc: Mike Rapoport <rppt@linux.vnet.ibm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: David Alan Gilbert <dgilbert@redhat.com>
Link: https://lkml.kernel.org/r/20201204203443.2714693-1-axelrasmussen@google.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
tools/testing/selftests/vm/userfaultfd.c

index 9b0912a..c442559 100644 (file)
@@ -206,19 +206,19 @@ static int hugetlb_release_pages(char *rel_area)
        return ret;
 }
 
-
 static void hugetlb_allocate_area(void **alloc_area)
 {
        void *area_alias = NULL;
        char **alloc_area_alias;
+
        *alloc_area = mmap(NULL, nr_pages * page_size, PROT_READ | PROT_WRITE,
                           (map_shared ? MAP_SHARED : MAP_PRIVATE) |
                           MAP_HUGETLB,
                           huge_fd, *alloc_area == area_src ? 0 :
                           nr_pages * page_size);
        if (*alloc_area == MAP_FAILED) {
-               fprintf(stderr, "mmap of hugetlbfs file failed\n");
-               *alloc_area = NULL;
+               perror("mmap of hugetlbfs file failed");
+               goto fail;
        }
 
        if (map_shared) {
@@ -227,14 +227,11 @@ static void hugetlb_allocate_area(void **alloc_area)
                                  huge_fd, *alloc_area == area_src ? 0 :
                                  nr_pages * page_size);
                if (area_alias == MAP_FAILED) {
-                       if (munmap(*alloc_area, nr_pages * page_size) < 0) {
-                               perror("hugetlb munmap");
-                               exit(1);
-                       }
-                       *alloc_area = NULL;
-                       return;
+                       perror("mmap of hugetlb file alias failed");
+                       goto fail_munmap;
                }
        }
+
        if (*alloc_area == area_src) {
                huge_fd_off0 = *alloc_area;
                alloc_area_alias = &area_src_alias;
@@ -243,6 +240,16 @@ static void hugetlb_allocate_area(void **alloc_area)
        }
        if (area_alias)
                *alloc_area_alias = area_alias;
+
+       return;
+
+fail_munmap:
+       if (munmap(*alloc_area, nr_pages * page_size) < 0) {
+               perror("hugetlb munmap");
+               exit(1);
+       }
+fail:
+       *alloc_area = NULL;
 }
 
 static void hugetlb_alias_mapping(__u64 *start, size_t len, unsigned long offset)