From: "Kiryl Shutsemau (Meta)" <[email protected]>

A collapse over a partially populated window has to zero the slots it
found empty.  The destination comes from the allocator holding whatever
was last written to it, and a slot the process never touched must read
back zero.

Nothing in the suite checks that.  validate_memory() only re-reads the
pattern fill_memory() wrote, and every caller passes it the faulted extent
alone, so a collapse that left stale bytes behind the holes would pass
every case here.

Check it where population and sub-PMD eligibility meet, which no existing
case covers either.  One page is faulted in a window-sized VMA; the case
expects the window collapsed, the faulted page unchanged, and every byte
behind the holes zero.

Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
---
 tools/testing/selftests/mm/khugepaged.c | 50 +++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/tools/testing/selftests/mm/khugepaged.c 
b/tools/testing/selftests/mm/khugepaged.c
index 85f138cfb9b2..b61e32566d47 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -1599,6 +1599,55 @@ static void collapse_order_sub_pmd_range(struct 
collapse_context *c,
        __collapse_order_sub_pmd_vma(c, ops, nr_windows, __func__);
 }
 
+/*
+ * A partially populated window in a sub-PMD VMA: population and
+ * sub-PMD eligibility at once. The unfaulted slots must come back
+ * zero-filled in the collapsed folio, and the faulted ones unchanged.
+ */
+static void collapse_order_sub_pmd_holes(struct collapse_context *c,
+                                        struct mem_ops *ops)
+{
+       size_t size = mthp_window_size();
+       char *bytes;
+       void *p;
+       size_t i;
+
+       mthp_push_target_order();
+
+       p = mmap(BASE_ADDR, size, PROT_READ | PROT_WRITE,
+                MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+       bytes = p;
+       if (p != BASE_ADDR)
+               ksft_exit_fail_msg("Failed to allocate VMA at %p\n", BASE_ADDR);
+
+       fill_memory(p, 0, page_size);
+       if (!window_not_collapsed(p, size))
+               ksft_exit_fail_msg("Unexpected large folio after fault\n");
+
+       madvise(p, size, MADV_HUGEPAGE);
+       ksft_print_msg("Collapse hole-y window inside a sub-PMD VMA...");
+       if (!khugepaged_wait_full_pass()) {
+               fail("Timeout");
+       } else if (window_collapsed(p, size)) {
+               /* The unfaulted tail must be zero-filled. */
+               for (i = page_size; i < size; i++) {
+                       if (bytes[i])
+                               break;
+               }
+               if (i == size)
+                       success("OK");
+               else
+                       fail("Fail");
+       } else {
+               fail("Fail");
+       }
+
+       validate_memory(p, 0, page_size);
+       munmap(p, size);
+       thp_pop_settings();
+       ksft_test_result_report(exit_status, "%s\n", __func__);
+}
+
 static void usage(void)
 {
        fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] <test type> 
[dir]\n\n");
@@ -1888,6 +1937,7 @@ int main(int argc, char **argv)
                TEST(collapse_order_mixed_sources, mthp_khugepaged_context, 
anon_ops);
                TEST(collapse_order_sub_pmd_vma, mthp_khugepaged_context, 
anon_ops);
                TEST(collapse_order_sub_pmd_range, mthp_khugepaged_context, 
anon_ops);
+               TEST(collapse_order_sub_pmd_holes, mthp_khugepaged_context, 
anon_ops);
        }
 
        TEST(collapse_full, madvise_context, anon_ops);
-- 
2.54.0


Reply via email to