From: Hyunwoo Kim <imv4bel@gmail.com>
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] mm/pagewalk: fix stale walk->action escaping walk_pmd_range()
Date: Mon, 10 Aug 2026 18:45:17 +0900 [thread overview]
Message-ID: <anmdrYGVqM-U4vlo@v4bel> (raw)
walk_pmd_range() resets walk->action in only one place in its loop body,
and that place is after the pmd_none() branch. For a walker with no
->install_pte, that branch continues to the next entry without passing the
reset. So if ->pmd_entry() sets ACTION_AGAIN and returns 0, and the PMD has
become none by the time the loop restarts at the again label, the reset is
skipped. If the remaining entries are all none too, the loop returns 0 with
ACTION_AGAIN still set. The ACTION_AGAIN that walk_pte_range() sets when
pte_offset_map_lock() fails escapes the same way.
Nothing looked at that value after walk_pmd_range() returned until commit
3b89863c3fa4 ("mm/pagewalk: fix race between concurrent split and refault")
turned that into a problem. It added both the PUD check that sets
ACTION_AGAIN before the loop is entered and the test in walk_pud_range()
that picks the value up right after walk_pmd_range() returns and walks
[addr, pud_addr_end(addr, end)) again. That is fine for the PUD check,
since none of walk_pmd_range()'s own callbacks have run at that point, but
a value that escaped as described above arrives after those callbacks have
already covered the range.
For mincore(2) this becomes an out-of-bounds write. ->pmd_entry() and
->pte_hole() advance the walk->private cursor by one byte per page, the
buffer is a single page from __get_free_page(), and mincore(2) asks for at
most PAGE_SIZE entries at a time, so there is no room to spare. Walking
the range a second time pushes the cursor past the end of the buffer, and
it does so again every time the race is hit. Reproducing this needs no
privileges: run mincore(2) over a 16 MiB anonymous mapping marked
MADV_NOHUGEPAGE while another thread repeatedly faults in a PMD-aligned
2 MiB range inside it and then drops it with madvise(MADV_DONTNEED).
Move the reset to the first statement of the loop body. walk_pud_range()
has the same shape and gets the same change; walk_p4d_range() never looks
at walk->action, so that hunk keeps the two functions in sync rather than
fixing a second bug.
Fixes: 3b89863c3fa4 ("mm/pagewalk: fix race between concurrent split and refault")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
---
mm/pagewalk.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 5d87c632a25507..d3bfece3193366 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -126,6 +126,7 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
pmd = pmd_offset(pud, addr);
do {
again:
+ walk->action = ACTION_SUBTREE;
next = pmd_addr_end(addr, end);
if (pmd_none(*pmd)) {
if (has_install)
@@ -138,8 +139,6 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
continue;
}
- walk->action = ACTION_SUBTREE;
-
/*
* This implies that each ->pmd_entry() handler
* needs to know about pmd_trans_huge() pmds
@@ -196,6 +195,7 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
pud = pud_offset(p4d, addr);
do {
again:
+ walk->action = ACTION_SUBTREE;
next = pud_addr_end(addr, end);
if (pud_none(*pud)) {
if (has_install)
@@ -208,8 +208,6 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
continue;
}
- walk->action = ACTION_SUBTREE;
-
if (ops->pud_entry)
err = ops->pud_entry(pud, addr, next, walk);
if (err)
--
2.43.0
next reply other threads:[~2026-08-10 9:45 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-10 9:45 Hyunwoo Kim [this message]
2026-08-10 11:19 ` [PATCH] mm/pagewalk: fix stale walk->action escaping walk_pmd_range() Lorenzo Stoakes (ARM)
2026-08-10 15:50 ` Hyunwoo Kim
2026-08-10 15:55 ` Lorenzo Stoakes (ARM)
2026-08-10 18:16 ` Andrew Morton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=anmdrYGVqM-U4vlo@v4bel \
--to=imv4bel@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=david@kernel.org \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=rppt@kernel.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox