From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 92D30455165 for ; Tue, 11 Aug 2026 16:20:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786465241; cv=none; b=SZlNNYt4RYfxDSLJlwQknUskxUDq/IcKKpswxaLJQ7V3uEWQjzTE/m9ud9Fy2PhYYd+u7EH/tlQz0492eJyk9EM9n43zipXNzjmNe1b4z+sOg/058nTMS9tEbJnTtl4nb9uD7jdNTQ9GZcYu1+q2ADYzsjYzJCw8aietqAYKRmU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786465241; c=relaxed/simple; bh=CcBCALKHKeEJKy7riguw0M6giIkjI1RkqhHj6hEblBY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FP6YFiJTE3D9Q44WdL/JG0SSJ0V7WJtEVKwvznCoxzrXAGz0GY6cI6FdeM9OxCbDXIGeKQDoGJhJi4xmkPBCSZ00fCBwTno4b9oxjJEjFgg6/AMTrutZCtP+mNzeNcO7ertV1QjVumchWPK6EMhq23XuUpznPaJJSosH/3ipU1I= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Zi7rnDJg; arc=none smtp.client-ip=209.85.214.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Zi7rnDJg" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2cf27856f9cso1112405ad.2 for ; Tue, 11 Aug 2026 09:20:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786465239; x=1787070039; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=xtZDYtapHGPfDE5SUp54DB2aVTRW+WWOIXswrMm3f4g=; b=Zi7rnDJgRTYr8eTsyw3WhhKkuzlY2YkXOWB+oH7tJDZgCb7k5fppaIMqqjRTLk/lxQ xouYnyXgJRQ94TKQW8zSDnk+xPuUTWAwv8DsrPEbEOOFlkQ7BWTlqcZZb87MxkH4KJCx R8jS9d8n95ZxRlA74zPXIG9OvzYl3HewC5RAp4ICW347FLDf04YmDzm9mbvhHomnHBo9 xbDIFu6YFA2vpp1fVJVsN3pxFZH/BALw3hw7o4+BcQPxrWec3J66gLO1DAqFtEp1ILZ8 TMFqd0qeScjYqL5D6hD3ZztY/j/M9Hb161NtQJ0Fdgx2pqjQ6sBe7PSpqCmTuPRuABym deBg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786465239; x=1787070039; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=xtZDYtapHGPfDE5SUp54DB2aVTRW+WWOIXswrMm3f4g=; b=HOnYS22qrum6PNHRHg1o1vBU3rHOJBFkYCimgUQFJU0Xd2CyBaWkruxOtAvy9+iH45 QDEoHhdaoExSi6L+jl5+3e98gH39fFMs6C+O82VHq3442ckXoDZgX2H0qvkgM7KNWfMu tC5uNIMG0Rqev8Vi4y3c7DYSAGRO7U9CiIY9ay+GJ0hJJ7l+RidCNDAA7lHLnioAPm6Q abkxSpE/05iJQB2fyuLvXYtTriVmcpYF+fx9savZgTX5fLifUVrl54yc7se3z1rJPd8Z P9q2e9OeYa0dh43rVZsWPqLgkoyXxcH3MUk15ga0pz92Axa+hdHExJoRsmUTWAui6QDo zuyQ== X-Forwarded-Encrypted: i=1; AHgh+RqpoTRFy0kQW7xNuPfqdqtJQPdSy09n4b2E7pdY5XOhA0x4psQQLLGUBqnq1lY/3fp2pC92yWVG+xEOZMw=@vger.kernel.org X-Gm-Message-State: AOJu0YxNq7xoP80iKiJ4AU8vB/5WttwTKHMjMepW05pHLIdTv8paNLSY x8wuOv6G1ij19Pdbr3RM6Fj7Gb6/pEnWjSLIQjcWKGZvfTjfGA8Q8bVk X-Gm-Gg: AR+sD11kFYeEYnq71waL35vt2thZm2hw9ycdUbRBifRYO4EiPU48qL9haiEYgwk0w/O K/523kpSdnWRp5Rh6pQ8I+QhFbzGbbSXMUIMsqY4qS/6MohWdV5lcyFtuY0cKpcbplLBaOatHb8 JKlk6LMgBOW29QnpJMfN8sdMasXxoWWbJF9g6aR1CwPH5ACIaaiMoW2dnfI/Z4i0VbMq5tzn84q Hdd2MrGo1OlRpxF/ztfuIXBrc+I5mmZ23xqtjuLL2LBJ1lSUfZufS+tViyqoZTX+m2zrazG+wj5 uXi697vINmR5TqF8KTrPHOb7pOEVKWlT8uMgvwiWYAoFx19b0d/zVAzrc5cOYZRJSGUA+ncBxTW LN2Ji7CAGQ9X6Lh9VlLMSKXfnUu6gQsZPmEU6/S6NusHnjCNQ9KUH2l4nWWPIOoYyeaXhgaq8+2 MTm1ckicZG0FcAfuyplAFVTF2FHclaVwmCP65fbagB4QBPj8dW6EE88/93s5dV4H+EyzkGq6GPk h21sc2kbo0= X-Received: by 2002:a17:903:947:b0:2ca:d151:383a with SMTP id d9443c01a7336-2d32c0cc361mr22791465ad.20.1786465238672; Tue, 11 Aug 2026 09:20:38 -0700 (PDT) Received: from v4bel.. ([58.123.110.97]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2d315f30027sm10547465ad.25.2026.08.11.09.20.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 11 Aug 2026 09:20:38 -0700 (PDT) From: Hyunwoo Kim To: akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org, liam@infradead.org, vbabka@kernel.org, rppt@kernel.org, surenb@google.com, mhocko@suse.com Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org, imv4bel@gmail.com Subject: [PATCH v2 2/2] selftests/mm: add stale walk->action race test Date: Wed, 12 Aug 2026 01:18:58 +0900 Message-ID: <20260811161949.3879321-3-imv4bel@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260811161949.3879321-1-imv4bel@gmail.com> References: <20260811161949.3879321-1-imv4bel@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The added pagewalk_race_test maps two PMDs and faults in 2MB of the first one. A second thread then faults in the second PMD and drops it again with MADV_DONTNEED in a loop, while the main thread reads Rss for the mapping from /proc/self/smaps. Clearing the second PMD while smaps_pte_range() runs leaves walk->action erroneously set to ACTION_AGAIN, which causes the PUD walk to be retried, so the first PMD is counted twice and Rss comes out twice as large as what was faulted in. mincore() is the caller named in the fix, but the second walk writes past the length mincore() copies back, so it cannot be seen from userspace there. smaps reports what the callbacks counted, so the duplicate shows up in Rss. A failure can only come from the kernel counting the same page twice, so missing the race is harmless. On an unfixed kernel the test fails after a few hundred reads at most and takes about half a second. Assisted-by: Claude:claude-opus-5 Signed-off-by: Hyunwoo Kim --- tools/testing/selftests/mm/.gitignore | 1 + tools/testing/selftests/mm/Makefile | 2 + tools/testing/selftests/mm/ksft_pagewalk.sh | 4 + .../testing/selftests/mm/pagewalk_race_test.c | 138 ++++++++++++++++++ tools/testing/selftests/mm/run_vmtests.sh | 2 + tools/testing/selftests/mm/vm_util.h | 1 + 6 files changed, 148 insertions(+) create mode 100755 tools/testing/selftests/mm/ksft_pagewalk.sh create mode 100644 tools/testing/selftests/mm/pagewalk_race_test.c diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore index 9ccd9e1447e66b..92f981f97740fd 100644 --- a/tools/testing/selftests/mm/.gitignore +++ b/tools/testing/selftests/mm/.gitignore @@ -66,3 +66,4 @@ merge prctl_thp_disable rmap folio_split_race_test +pagewalk_race_test diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index e6df968f0971c8..cde9b22f121d4b 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -105,6 +105,7 @@ TEST_GEN_FILES += guard-regions TEST_GEN_FILES += merge TEST_GEN_FILES += rmap TEST_GEN_FILES += folio_split_race_test +TEST_GEN_FILES += pagewalk_race_test ifneq ($(ARCH),arm64) TEST_GEN_FILES += soft-dirty @@ -163,6 +164,7 @@ TEST_PROGS += ksft_mlock.sh TEST_PROGS += ksft_mmap.sh TEST_PROGS += ksft_mremap.sh TEST_PROGS += ksft_pagemap.sh +TEST_PROGS += ksft_pagewalk.sh TEST_PROGS += ksft_pfnmap.sh TEST_PROGS += ksft_pkey.sh TEST_PROGS += ksft_process_madv.sh diff --git a/tools/testing/selftests/mm/ksft_pagewalk.sh b/tools/testing/selftests/mm/ksft_pagewalk.sh new file mode 100755 index 00000000000000..6f6c3ee1c13ef4 --- /dev/null +++ b/tools/testing/selftests/mm/ksft_pagewalk.sh @@ -0,0 +1,4 @@ +#!/bin/sh -e +# SPDX-License-Identifier: GPL-2.0 + +./run_vmtests.sh -t pagewalk diff --git a/tools/testing/selftests/mm/pagewalk_race_test.c b/tools/testing/selftests/mm/pagewalk_race_test.c new file mode 100644 index 00000000000000..42fd6e75e821ed --- /dev/null +++ b/tools/testing/selftests/mm/pagewalk_race_test.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Regression test for a stale walk->action escaping walk_pmd_range() and + * making walk_pud_range() walk the same range twice. + * + * The mapping is two PMDs inside one PUD. PMD 0 is populated once and left + * alone, PMD 1 is faulted in and dropped again by a second thread. Clearing + * PMD 1 under smaps_pte_range() makes it raise ACTION_AGAIN, and since it is + * the last entry the stale value leaves walk_pmd_range(), so smaps accounts + * PMD 0 twice. A kernel that does not reclaim the emptied page table never + * clears PMD 1 and so never hits the race. + * + * A hit can only come from the kernel counting the same page twice, so the + * test cannot fail spuriously. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "vm_util.h" +#include "kselftest.h" + +#define NR_PMDS 2 +#define NR_ROUNDS 20000 +/* Cap on how much of PMD 0 to fault in, so that a large PMD stays cheap. */ +#define POP_MAX (2 * 1024 * 1024) + +static char *area; +static size_t pmd_size; +static atomic_int stop; + +static void *racer(void *arg) +{ + char *pmd1 = area + pmd_size; + + while (atomic_load_explicit(&stop, memory_order_acquire) == 0) { + /* madvise() below keeps the compiler from lifting this out. */ + *pmd1 = 1; + madvise(pmd1, pmd_size, MADV_DONTNEED); + } + return NULL; +} + +static unsigned long smaps_rss_kb(void) +{ + char buf[1024]; + char *entry; + + entry = __get_smap_entry(area, "Rss:", buf, sizeof(buf)); + if (!entry) + ksft_exit_fail_msg("no Rss: entry for the test mapping\n"); + + return strtoul(entry, NULL, 10); +} + +int main(void) +{ + unsigned long max_rss_kb, rss_kb = 0; + size_t size, pop_size, i; + pthread_t thread; + char *raw; + + ksft_print_header(); + + pmd_size = read_pmd_pagesize(); + if (!pmd_size) + ksft_exit_skip("Cannot determine PMD size\n"); + + if (sysconf(_SC_NPROCESSORS_ONLN) < 2) + ksft_exit_skip("Need at least 2 CPUs to race\n"); + + size = NR_PMDS * pmd_size; + + /* + * Align to the mapping size to stay inside one PUD, then trim the + * slack so that smaps has exactly one VMA to report. + */ + raw = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (raw == MAP_FAILED) + ksft_exit_fail_msg("mmap failed\n"); + + area = (char *)(((unsigned long)raw + size - 1) & ~(size - 1)); + if (area != raw) + munmap(raw, area - raw); + if (raw + 2 * size != area + size) + munmap(area + size, raw + 2 * size - (area + size)); + + /* A huge PMD never reaches pte_offset_map_lock(), so keep them out. */ + if (madvise(area, size, MADV_NOHUGEPAGE)) + ksft_exit_skip("MADV_NOHUGEPAGE failed\n"); + + pop_size = pmd_size < POP_MAX ? pmd_size : POP_MAX; + memset(area, 1, pop_size); + + max_rss_kb = (pop_size >> 10) + 256; + + /* Over the limit before racing means this is not our own mapping. */ + rss_kb = smaps_rss_kb(); + if (rss_kb > max_rss_kb) + ksft_exit_fail_msg("Rss is %lu kB before racing, expected at most %lu kB\n", + rss_kb, max_rss_kb); + + ksft_set_plan(1); + ksft_print_msg("racing smaps against MADV_DONTNEED, %d rounds\n", + NR_ROUNDS); + + if (pthread_create(&thread, NULL, racer, NULL)) + ksft_exit_fail_msg("pthread_create failed\n"); + + for (i = 0; i < NR_ROUNDS; i++) { + rss_kb = smaps_rss_kb(); + if (rss_kb > max_rss_kb) + break; + } + + atomic_store_explicit(&stop, 1, memory_order_release); + pthread_join(thread, NULL); + + if (i < NR_ROUNDS) { + ksft_print_msg("walk ran twice over the same range\n"); + ksft_test_result_fail("Rss %lu kB exceeds %lu kB, round %zu\n", + rss_kb, max_rss_kb, i); + } else { + ksft_test_result_pass("Rss within %lu kB over %d rounds\n", + max_rss_kb, NR_ROUNDS); + } + + ksft_exit(i == NR_ROUNDS); + + return 0; +} diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh index 8c296dedf0474d..d90c6370814f7a 100755 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -398,6 +398,8 @@ fi CATEGORY="pagemap" run_test ./pagemap_ioctl +CATEGORY="pagewalk" run_test ./pagewalk_race_test + CATEGORY="pfnmap" run_test ./pfnmap # COW tests diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h index ea8fc8fdf0eb0b..62292e2417d162 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -88,6 +88,7 @@ bool pagemap_is_populated(int fd, char *start); unsigned long pagemap_get_pfn(int fd, char *start); void clear_softdirty(void); bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len); +char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len); uint64_t read_pmd_pagesize(void); unsigned long rss_anon(void); bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size); -- 2.43.0