Pss metrics can be affected by map count even if VMA tree stays unchanged, therefore tests checking VMA tree integrity can't use them. Use Rss instead and pre-populate the VMAs so that Rss changes if a VMA gets skipped or double-counted. To ensure all test VMAs are populated with anonymous pages, remain mergeable across VMA operations, and do not have their Rss changed by memory reclaim: - Toggle PROT_EXEC instead of PROT_WRITE between adjacent test VMAs so all test VMAs remain writable and MAP_POPULATE faults in anonymous pages (rather than the shared zero page, which is not accounted in Rss). - Use mprotect() instead of mmap(..., MAP_FIXED) in split_vma() and merge_vma() so the first page of the split VMA stays resident. mprotect(..., PROT_NONE) splits the VMA without unmapping the first page (pte_present() remains true for PAGE_NONE PTEs), and mprotect(..., self->mod_info->prot) merges it back. Both parts of the split VMA retain their non-zero Rss. - Lock all present pages in the child with mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) so memory pressure cannot reclaim pages and alter Rss during the test. - In remap_vma(), unlock next_addr before mremap(..., MREMAP_DONTUNMAP) so next_addr is not split, use mmap(..., MAP_FIXED) in patch_vma() to merge the middle VMA back into addr, and exclude smaps_rollup tests from test_maps_tearing_from_remap since remapping moves and unmaps the page from next_addr. Fixes: ca5480fb53fa ("selftests/proc: add /proc/pid/smaps_rollup tearing tests") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202610271934.fd287c3f-lkp@intel.com Signed-off-by: Suren Baghdasaryan --- Changes from v1 [1]: - fixed the way the odd VMAs are populated to avoid mapping zero pages - used mprotect for splitting/merging VMAs to prevent split_vma() from unmapping and freeing the first page - mlock all areas to keep Rss unchanged under memory pressure, per Pedro Falcato - exclude smaps_rollup tests from test_maps_tearing_from_remap because remapping a populated VMA affects Rss metric [1] https://lore.kernel.org/all/20261028051312.1505295-1-surenb@google.com/ tools/testing/selftests/proc/proc-maps-race.c | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/tools/testing/selftests/proc/proc-maps-race.c b/tools/testing/selftests/proc/proc-maps-race.c index bf4c5073f6fc..6025aea68190 100644 --- a/tools/testing/selftests/proc/proc-maps-race.c +++ b/tools/testing/selftests/proc/proc-maps-race.c @@ -528,13 +528,10 @@ static bool compare_smaps_rollup(FIXTURE_DATA(proc_maps_race) *self, struct smaps_rollup_stats *actual) { /* - * Clean/dirty metrics might change but Pss-related ones - * should stay constant. + * Clean/dirty metrics might change. Pss depends on map count and might + * change as well. Rss should stay constant. */ - if (cmp_smaps_rollup_stat(expected, actual, Pss) && - cmp_smaps_rollup_stat(expected, actual, Pss_Anon) && - cmp_smaps_rollup_stat(expected, actual, Pss_File) && - cmp_smaps_rollup_stat(expected, actual, Pss_Shmem)) + if (cmp_smaps_rollup_stat(expected, actual, Rss)) return true; print_smaps_rollup_stats("Expected stats:", self, expected); @@ -637,14 +634,13 @@ static bool query_addr_at(int maps_fd, void *addr, static inline bool split_vma(FIXTURE_DATA(proc_maps_race) *self) { /* PROT_NONE differs from both readable neighbors. */ - return mmap(self->mod_info->addr, self->page_size, PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; + return mprotect(self->mod_info->addr, self->page_size, PROT_NONE) == 0; } static inline bool merge_vma(FIXTURE_DATA(proc_maps_race) *self) { - return mmap(self->mod_info->addr, self->page_size, self->mod_info->prot, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; + return mprotect(self->mod_info->addr, self->page_size, + self->mod_info->prot) == 0; } static inline bool check_split_result(struct line_content *mod_last_line, @@ -681,6 +677,12 @@ static inline bool check_shrink_result(struct line_content *mod_last_line, static inline bool remap_vma(FIXTURE_DATA(proc_maps_race) *self) { + /* + * Unlock next_addr so MREMAP_DONTUNMAP does not split it. + */ + if (munlock(self->mod_info->next_addr, self->page_size * 3) != 0) + return false; + /* * Remap the last page of the next vma into the middle of the vma. * This splits the current vma and the first and middle parts (the @@ -694,8 +696,9 @@ static inline bool remap_vma(FIXTURE_DATA(proc_maps_race) *self) static inline bool patch_vma(FIXTURE_DATA(proc_maps_race) *self) { - return mprotect(self->mod_info->addr + self->page_size, self->page_size, - self->mod_info->prot) == 0; + return mmap(self->mod_info->addr + self->page_size, self->page_size, + self->mod_info->prot, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED; } static inline bool check_remap_result(struct line_content *mod_last_line, @@ -759,11 +762,17 @@ FIXTURE_SETUP(proc_maps_race) for (i = 0; i < mod_info->vma_count; i++) { mod_info->child_mapped_addr[i] = mmap(NULL, self->page_size * 3, prot, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); ASSERT_NE(mod_info->child_mapped_addr[i], MAP_FAILED); /* change protection in adjacent maps to prevent merging */ - prot ^= PROT_WRITE; + prot ^= PROT_EXEC; } + /* + * Lock all present pages in memory so memory reclaim cannot + * change RSS during the test, and use MCL_ONFAULT so + * mremap() expansion does not populate new pages. + */ + ASSERT_EQ(mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT), 0); signal_state(mod_info, CHILD_READY); wait_for_state(mod_info, PARENT_READY); while (true) { @@ -833,10 +842,10 @@ FIXTURE_SETUP(proc_maps_race) for (int i = 0; i < mod_info->vma_count; i++) { if (mod_info->child_mapped_addr[i] == (void *)self->last_line.start_addr) { mod_info->addr = mod_info->child_mapped_addr[i]; - mod_info->prot = PROT_READ; - /* Even VMAs have write permission */ - if ((i % 2) == 0) - mod_info->prot |= PROT_WRITE; + mod_info->prot = PROT_READ | PROT_WRITE; + /* Odd VMAs have exec permission */ + if ((i % 2) != 0) + mod_info->prot |= PROT_EXEC; } else if (mod_info->child_mapped_addr[i] == (void *)self->first_line.start_addr) { mod_info->next_addr = mod_info->child_mapped_addr[i]; } @@ -1066,7 +1075,6 @@ TEST_F(proc_maps_race, test_maps_tearing_from_remap) struct line_content remapped_first_line; struct line_content restored_last_line; struct line_content restored_first_line; - struct smaps_rollup_stats orig_stats; wait_for_state(mod_info, SETUP_READY); @@ -1080,8 +1088,6 @@ TEST_F(proc_maps_race, test_maps_tearing_from_remap) report_test_start("Tearing from remap", self->verbose); ASSERT_TRUE(capture_mod_pattern(self, &remapped_last_line, &remapped_first_line, &restored_last_line, &restored_first_line)); - if (self->maps_file == SMAPS) - ASSERT_TRUE(parse_smaps_rollup(self, &orig_stats)); /* Now start concurrent modifications for self->duration_sec */ signal_state(mod_info, TEST_READY); @@ -1134,11 +1140,6 @@ TEST_F(proc_maps_race, test_maps_tearing_from_remap) vma_end - vma_start == self->page_size * 3) || (vma_start == self->last_line.start_addr + self->page_size && vma_end - vma_start == self->page_size)); - } else { - struct smaps_rollup_stats stats; - - ASSERT_TRUE(parse_smaps_rollup(self, &stats)); - ASSERT_TRUE(compare_smaps_rollup(self, &orig_stats, &stats)); } clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); end_test_iteration(&end_ts, self->verbose); base-commit: b61fa14ce6bcbba2cb9a96feea31b760226d6b43 -- 2.56.0.317.g454f8302e1-goog