selftests: mm: add a test for remapping within a range
authorJoel Fernandes (Google) <joel@joelfernandes.org>
Sun, 3 Sep 2023 15:13:27 +0000 (15:13 +0000)
committerAndrew Morton <akpm@linux-foundation.org>
Wed, 4 Oct 2023 17:32:20 +0000 (10:32 -0700)
Move a block of memory within a memory range.  Any alignment optimization
on the source address may cause corruption.  Verify using kselftest that
it works.  I have also verified with tracing that such optimization does
not happen due to this check in can_align_down():

if (!for_stack && vma->vm_start != addr_to_align)
return false;

Link: https://lkml.kernel.org/r/20230903151328.2981432-7-joel@joelfernandes.org
Signed-off-by: Joel Fernandes (Google) <joel@joelfernandes.org>
Reviewed-by: Lorenzo Stoakes <lstoakes@gmail.com>
Cc: Kalesh Singh <kaleshsingh@google.com>
Cc: "Kirill A. Shutemov" <kirill@shutemov.name>
Cc: Liam R. Howlett <Liam.Howlett@oracle.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Lokesh Gidra <lokeshgidra@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Paul E. McKenney <paulmck@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
tools/testing/selftests/mm/mremap_test.c

index d7366074e2a888eed0b52281a121c362ef668220..12a095457f4c20238619a0a3d1a6e6e2b6baf4d5 100644 (file)
@@ -23,6 +23,7 @@
 #define VALIDATION_NO_THRESHOLD 0      /* Verify the entire region */
 
 #define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
+#define SIZE_MB(m) ((size_t)m * (1024 * 1024))
 
 struct config {
        unsigned long long src_alignment;
@@ -226,6 +227,79 @@ out:
                ksft_test_result_fail("%s\n", test_name);
 }
 
+/*
+ * Verify that an mremap within a range does not cause corruption
+ * of unrelated part of range.
+ *
+ * Consider the following range which is 2MB aligned and is
+ * a part of a larger 20MB range which is not shown. Each
+ * character is 256KB below making the source and destination
+ * 2MB each. The lower case letters are moved (s to d) and the
+ * upper case letters are not moved. The below test verifies
+ * that the upper case S letters are not corrupted by the
+ * adjacent mremap.
+ *
+ * |DDDDddddSSSSssss|
+ */
+static void mremap_move_within_range(char pattern_seed)
+{
+       char *test_name = "mremap mremap move within range";
+       void *src, *dest;
+       int i, success = 1;
+
+       size_t size = SIZE_MB(20);
+       void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+                        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+       if (ptr == MAP_FAILED) {
+               perror("mmap");
+               success = 0;
+               goto out;
+       }
+       memset(ptr, 0, size);
+
+       src = ptr + SIZE_MB(6);
+       src = (void *)((unsigned long)src & ~(SIZE_MB(2) - 1));
+
+       /* Set byte pattern for source block. */
+       srand(pattern_seed);
+       for (i = 0; i < SIZE_MB(2); i++) {
+               ((char *)src)[i] = (char) rand();
+       }
+
+       dest = src - SIZE_MB(2);
+
+       void *new_ptr = mremap(src + SIZE_MB(1), SIZE_MB(1), SIZE_MB(1),
+                                                  MREMAP_MAYMOVE | MREMAP_FIXED, dest + SIZE_MB(1));
+       if (new_ptr == MAP_FAILED) {
+               perror("mremap");
+               success = 0;
+               goto out;
+       }
+
+       /* Verify byte pattern after remapping */
+       srand(pattern_seed);
+       for (i = 0; i < SIZE_MB(1); i++) {
+               char c = (char) rand();
+
+               if (((char *)src)[i] != c) {
+                       ksft_print_msg("Data at src at %d got corrupted due to unrelated mremap\n",
+                                      i);
+                       ksft_print_msg("Expected: %#x\t Got: %#x\n", c & 0xff,
+                                       ((char *) src)[i] & 0xff);
+                       success = 0;
+               }
+       }
+
+out:
+       if (munmap(ptr, size) == -1)
+               perror("munmap");
+
+       if (success)
+               ksft_test_result_pass("%s\n", test_name);
+       else
+               ksft_test_result_fail("%s\n", test_name);
+}
+
 /*
  * Returns the start address of the mapping on success, else returns
  * NULL on failure.
@@ -491,6 +565,7 @@ int main(int argc, char **argv)
        unsigned int threshold_mb = VALIDATION_DEFAULT_THRESHOLD;
        unsigned int pattern_seed;
        int num_expand_tests = 2;
+       int num_misc_tests = 1;
        struct test test_cases[MAX_TEST] = {};
        struct test perf_test_cases[MAX_PERF_TEST];
        int page_size;
@@ -572,7 +647,7 @@ int main(int argc, char **argv)
                                (threshold_mb * _1MB >= _1GB);
 
        ksft_set_plan(ARRAY_SIZE(test_cases) + (run_perf_tests ?
-                     ARRAY_SIZE(perf_test_cases) : 0) + num_expand_tests);
+                     ARRAY_SIZE(perf_test_cases) : 0) + num_expand_tests + num_misc_tests);
 
        for (i = 0; i < ARRAY_SIZE(test_cases); i++)
                run_mremap_test_case(test_cases[i], &failures, threshold_mb,
@@ -590,6 +665,8 @@ int main(int argc, char **argv)
 
        fclose(maps_fp);
 
+       mremap_move_within_range(pattern_seed);
+
        if (run_perf_tests) {
                ksft_print_msg("\n%s\n",
                 "mremap HAVE_MOVE_PMD/PUD optimization time comparison for 1GB region:");