Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Kiryl Shutsemau (Meta)" <kas@kernel.org>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	"Linus Torvalds" <torvalds@linux-foundation.org>,
	"David Hildenbrand" <david@kernel.org>,
	"Matthew Wilcox" <willy@infradead.org>,
	"Vlastimil Babka" <vbabka@kernel.org>,
	"Lorenzo Stoakes" <ljs@kernel.org>, "Jan Kara" <jack@suse.cz>,
	"Hugh Dickins" <hughd@google.com>,
	"Christian Brauner" <brauner@kernel.org>,
	"Alexander Viro" <viro@zeniv.linux.org.uk>,
	"Harry Yoo" <harry@kernel.org>
Cc: "Miklos Szeredi" <miklos@szeredi.hu>,
	"Miaohe Lin" <linmiaohe@huawei.com>,
	"Naoya Horiguchi" <nao.horiguchi@gmail.com>,
	"Oscar Salvador" <osalvador@suse.de>,
	"Liam R. Howlett" <liam@infradead.org>,
	"Mike Rapoport" <rppt@kernel.org>,
	"Suren Baghdasaryan" <surenb@google.com>,
	"Michal Hocko" <mhocko@suse.com>,
	"Brendan Jackman" <brendan.jackman@linux.dev>,
	"Johannes Weiner" <hannes@cmpxchg.org>, "Zi Yan" <ziy@nvidia.com>,
	"Baolin Wang" <baolin.wang@linux.alibaba.com>,
	"Nico Pache" <nico.pache@linux.dev>,
	"Ryan Roberts" <ryan.roberts@arm.com>,
	"Dev Jain" <dev.jain@arm.com>, "Barry Song" <baohua@kernel.org>,
	"Lance Yang" <lance.yang@linux.dev>,
	"Usama Arif" <usama.arif@linux.dev>,
	"Kairui Song" <kasong@tencent.com>,
	"Qi Zheng" <qi.zheng@linux.dev>,
	"Shakeel Butt" <shakeel.butt@linux.dev>,
	"Axel Rasmussen" <axelrasmussen@google.com>,
	"Yuanchu Xie" <yuanchu@google.com>, "Wei Xu" <weixugc@google.com>,
	"Baoquan He" <baoquan.he@linux.dev>,
	"Paul E. McKenney" <paulmck@kernel.org>,
	"Pedro Falcato" <pfalcato@suse.de>,
	"Luis Chamberlain" <mcgrof@kernel.org>,
	"Ilya Gladyshev" <ilya.gladyshev@linux.dev>,
	"Jaegeuk Kim" <jaegeuk@kernel.org>,
	linux-mm@kvack.org, kernel-team@meta.com
Subject: Re: [RFC PATCH v2 14/18] mm/filemap: read large folios without folio references
Date: Thu, 05 Nov 2026 13:03:44 +0000	[thread overview]
Message-ID: <sashiko-outbox-199107@kernel.org> (raw)
In-Reply-To: <20261104130118.2507513-15-kas@kernel.org>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm/filemap: lockless large folio read can bypass hwpoison check and copy poisoned subpage
--

commit 3f27d3e89354814e80ba93e8767ff8878253a593
Author: Kiryl Shutsemau (Meta) <kas@kernel.org>

mm/filemap: read large folios without folio references

This patch extends lockless RCU pagecache reads to support large folios
without taking folio references. It checks whether the last page of the
read range still points to the same folio in the XArray and copies data
page by page via _copy_to_iter().

> diff --git a/mm/filemap.c b/mm/filemap.c
> index e99705a66c1d4..182ae7d5db56a 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -2834,15 +2834,16 @@ static bool filemap_read_rcu_can_copy(struct folio *folio, pgoff_t index,
>  		return false;
>  	if (folio_test_readahead(folio) || folio_test_dropbehind(folio))
>  		return false;
> -	/* One reference covers all of a large folio's pages */
> -	if (folio_test_large(folio))
> -		return false;
>  	/* A split clears PG_head before it puts the new folios in their slots */
> -	if (folio->index != index)
> +	if (!folio_test_large(folio) && folio->index != index)
>  		return false;
>  	/* memory_failure() waits only for readers that got past this */
>  	if (folio_test_hwpoison(folio))
>  		return false;
> +	/* Not folio_test_has_hwpoisoned(): it asserts PG_head, a split clears it */
> +	if (folio_test_large(folio) &&
> +	    test_bit(PG_has_hwpoisoned, &folio->_flags_1))
> +		return false;
>  
>  	/*
>  	 * The slow path does not call folio_mark_accessed() on the folio that
> @@ -2896,6 +2897,62 @@ static struct folio *filemap_read_rcu_lookup(struct xa_state *xas,
>  	return folio;
>  }
>  
> +/*
> + * How much of @bytes, starting @offset into page @index, can be copied from
> + * @folio.  A folio is in all the slots of its range or in none: when the slot
> + * of the last page holds it too, so do the slots in between, and the pages
> + * are this folio's.
> + */
> +static size_t filemap_read_rcu_size(struct xa_state *xas, struct folio *folio,
> +		pgoff_t index, size_t offset, size_t bytes)
> +{
> +	pgoff_t end = index + DIV_ROUND_UP(offset + bytes, PAGE_SIZE);
> +	unsigned int order;
> +	pgoff_t next;
> +
> +	if (end - index > 1 && folio_test_large(folio)) {
> +		/* A split can change it under us: it only picks the page to check */
> +		order = min(folio_large_order(folio), MAX_PAGECACHE_ORDER);
> +		next = folio->index + (1UL << order);
> +		if (next > index && next < end)
> +			end = next;
> +		xas_set(xas, end - 1);
> +		/* A split after this copies PG_rcu_free to the new folios */
> +		if (xas_load(xas) == folio)
> +			return min(bytes, (end - index) * PAGE_SIZE - offset);
> +	}
> +	return min(bytes, PAGE_SIZE - offset);
> +}
> +
> +/*
> + * Copy @bytes from @folio, starting @offset into its page at @index.  Page
> + * by page, as copy_page_to_iter() does, without its checks for each call.
> + * Not copy_to_iter(): its usercopy check reads the folio's order, which a
> + * split can change.
> + */
> +static size_t filemap_read_rcu_copy(struct folio *folio, pgoff_t index,
> +		size_t offset, size_t bytes, struct iov_iter *iter)
> +{
> +	struct page *page = folio_page(folio, index - folio->index);
> +	size_t n = 0;
> +
> +	while (n < bytes) {
> +		size_t len = min(bytes - n, PAGE_SIZE - offset);
> +		size_t done;
> +		void *kaddr;
> +
> +		kaddr = kmap_local_page(page);
> +		done = _copy_to_iter(kaddr + offset, len, iter);

[Severity: High]
As reported by Sashiko on RFC [1], can a lockless reader in
filemap_read_rcu_batch() bypass the hwpoison checks in
filemap_read_rcu_can_copy() and copy from a hardware-poisoned tail subpage in
filemap_read_rcu_copy()?

When a hardware memory error hits a tail page p of a large folio,
memory_failure() sets PG_hwpoison on the tail struct page p rather than on
&folio->page, so folio_test_hwpoison(folio) in filemap_read_rcu_can_copy()
only checks subpage 0. Meanwhile, filemap_read_rcu_copy() copies each subpage
via _copy_to_iter() without checking PageHWPoison(page), relying on
filemap_read_rcu_can_copy() to reject the folio beforehand.

However, there appear to be three ways a poisoned tail subpage can get past
filemap_read_rcu_can_copy():

1. During a split, __split_folio_to_order() in mm/huge_memory.c clears
PG_has_hwpoisoned on the original folio at the start of the split, before the
new sub-folios are initialized, before ClearPageCompound() clears PG_head,
and before __folio_freeze_and_split_unmapped() updates the XArray slots in
mapping->i_pages:

mm/huge_memory.c:__split_folio_to_order() {
	...
	folio_clear_has_hwpoisoned(folio);

	/* Check first new_nr_pages since the loop below skips them */
	if (handle_hwpoison &&
	    page_range_has_hwpoisoned(folio_page(folio, 0), new_nr_pages))
		folio_set_has_hwpoisoned(folio);
	...
}

While the XArray slots still point to folio, folio_test_large(folio) remains
true (including across splits to new_order > 0 when the poisoned subpage is
at index >= folio->index + (1UL << new_order)) while PG_has_hwpoisoned is 0.
A reader looking up the poisoned tail index or sizing a multi-page read
across the folio in filemap_read_rcu_size() will see the folio still in its
slots with PG_has_hwpoisoned cleared and proceed to copy the poisoned subpage.

2. filemap_read_rcu_can_copy() evaluates folio_test_large(folio) twice
without caching the result:

	if (!folio_test_large(folio) && folio->index != index)
		return false;
	if (folio_test_hwpoison(folio))
		return false;
	if (folio_test_large(folio) &&
	    test_bit(PG_has_hwpoisoned, &folio->_flags_1))
		return false;

If a concurrent split to order 0 clears PG_has_hwpoisoned and PG_head between
the two folio_test_large(folio) calls while reading at index != folio->index,
the first check sees folio_test_large(folio) as true (skipping the
folio->index != index check) and the second check sees it as false (skipping
the PG_has_hwpoisoned check). filemap_read_rcu_size() then returns a single
page and filemap_read_rcu_copy() copies from folio_page(folio, index -
folio->index).

3. In mm/memory-failure.c, memory_failure() sets PG_hwpoison on the tail page
p via TestSetPageHWPoison(p) before calling get_hwpoison_page() and before
setting PG_has_hwpoisoned on the folio. If get_hwpoison_page() fails (for
example, while the folio refcount is transiently frozen), PG_hwpoison remains
set on tail page p while PG_has_hwpoisoned is never set on the folio. If a
subsequent memory_failure() call hits TestSetPageHWPoison(p), it calls
synchronize_rcu() and returns 0 so the caller can unmap p from the kernel
direct map, after which a lockless reader copying from p in
filemap_read_rcu_copy() can trigger an unhandled kernel page fault.

Would checking PageHWPoison(page) on each subpage in filemap_read_rcu_copy()
(or before copying) avoid reading from a poisoned tail page across these
races?

[1] https://lore.kernel.org/r/20261019154913.1765974-16-kas@kernel.org

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261104130118.2507513-1-kas@kernel.org?part=14


  reply	other threads:[~2026-11-05 13:03 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 13:00 [RFC PATCH v2 00/18] mm: read the page cache without folio references Kiryl Shutsemau (Meta)
2026-11-04 13:00 ` [RFC PATCH v2 01/18] mm: free PG_rcu_free folios only after an RCU grace period Kiryl Shutsemau (Meta)
2026-11-04 13:00 ` [RFC PATCH v2 02/18] splice: don't steal page cache folios Kiryl Shutsemau (Meta)
2026-11-04 16:05   ` David Hildenbrand (Arm)
2026-11-05 14:55     ` Kiryl Shutsemau
2026-11-04 13:00 ` [RFC PATCH v2 03/18] fuse: don't move a folio with PG_rcu_free into a fuse file Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 04/18] mm: count the pages waiting for a grace period Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 05/18] mm: expedite the grace period when a CPU has many deferred frees Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 06/18] mm: free the pages waiting for a grace period before OOM Kiryl Shutsemau (Meta)
2026-11-05 13:03   ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 07/18] mm: free the pages waiting for a grace period before isolation fails Kiryl Shutsemau (Meta)
2026-11-05 13:03   ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 08/18] mm/compaction: free the migrated folios that wait for a grace period Kiryl Shutsemau (Meta)
2026-11-05 13:03   ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 09/18] mm/memory-failure: handle folios with PG_rcu_free Kiryl Shutsemau (Meta)
2026-11-05 13:03   ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 10/18] mm: have folio_mark_accessed() return whether the folio is hot Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 11/18] mm/filemap: mark a folio accessed in one place in filemap_read() Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 12/18] mm/filemap: set PG_rcu_free on the folios that reads keep coming back to Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 13/18] mm/filemap: read from the page cache without folio references Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 14/18] mm/filemap: read large folios " Kiryl Shutsemau (Meta)
2026-11-05 13:03   ` sashiko-bot [this message]
2026-11-04 13:01 ` [RFC PATCH v2 15/18] mm/filemap: don't store f_ra.prev_pos for FMODE_RANDOM files Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 16/18] mm/shmem: count accesses to a folio the way filemap_read() does Kiryl Shutsemau (Meta)
2026-11-04 13:01 ` [RFC PATCH v2 17/18] mm/shmem: read without folio references Kiryl Shutsemau (Meta)
2026-11-05 13:03   ` sashiko-bot
2026-11-04 13:01 ` [RFC PATCH v2 18/18] mm/filemap: add a debugfs switch for lockless page cache reads Kiryl Shutsemau (Meta)

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=sashiko-outbox-199107@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=axelrasmussen@google.com \
    --cc=baohua@kernel.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=baoquan.he@linux.dev \
    --cc=brauner@kernel.org \
    --cc=brendan.jackman@linux.dev \
    --cc=david@kernel.org \
    --cc=dev.jain@arm.com \
    --cc=hannes@cmpxchg.org \
    --cc=harry@kernel.org \
    --cc=hughd@google.com \
    --cc=ilya.gladyshev@linux.dev \
    --cc=jack@suse.cz \
    --cc=jaegeuk@kernel.org \
    --cc=kas@kernel.org \
    --cc=kasong@tencent.com \
    --cc=kernel-team@meta.com \
    --cc=lance.yang@linux.dev \
    --cc=liam@infradead.org \
    --cc=linmiaohe@huawei.com \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=mhocko@suse.com \
    --cc=miklos@szeredi.hu \
    --cc=nao.horiguchi@gmail.com \
    --cc=nico.pache@linux.dev \
    --cc=osalvador@suse.de \
    --cc=paulmck@kernel.org \
    --cc=pfalcato@suse.de \
    --cc=qi.zheng@linux.dev \
    --cc=rppt@kernel.org \
    --cc=ryan.roberts@arm.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=shakeel.butt@linux.dev \
    --cc=surenb@google.com \
    --cc=torvalds@linux-foundation.org \
    --cc=usama.arif@linux.dev \
    --cc=vbabka@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    --cc=weixugc@google.com \
    --cc=willy@infradead.org \
    --cc=yuanchu@google.com \
    --cc=ziy@nvidia.com \
    /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