The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: "David Hildenbrand (Arm)" <david@kernel.org>
To: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>,
	Zi Yan <ziy@nvidia.com>,
	Baolin Wang <baolin.wang@linux.alibaba.com>,
	"Liam R. Howlett" <liam@infradead.org>,
	Nico Pache <npache@redhat.com>,
	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>,
	Miaohe Lin <linmiaohe@huawei.com>,
	Naoya Horiguchi <nao.horiguchi@gmail.com>,
	Wei Yang <richard.weiyang@gmail.com>,
	linux-mm@kvack.org, linux-kernel@vger.kernel.org,
	Luis Chamberlain <mcgrof@kernel.org>,
	Li Youhong <liyouhong@kylinos.cn>,
	stable@vger.kernel.org
Subject: Re: [PATCH] mm/memory-failure: fix folio refcount leak and min_order_for_split() locking
Date: Thu, 6 Aug 2026 16:17:21 +0200	[thread overview]
Message-ID: <d6e665cd-08a3-4598-8a4f-1af21eea4132@kernel.org> (raw)
In-Reply-To: <anRv8ngsyOjkgOHM@lucifer>

On 8/6/26 16:03, Lorenzo Stoakes (ARM) wrote:
> On Thu, Aug 06, 2026 at 01:14:38PM +0200, David Hildenbrand (Arm) wrote:
>> hwpoison code can end up calling min_order_for_split() without holding
>> the folio lock. There isn't really something that would prevent
>> concurrent folio split. Consequently folio->mapping can get set to
>> NULL after checking for "!folio->mapping", and if the compiler
>> reloads folio->mapping, mapping_min_folio_order() would try to
>> dereference NULL.
>>
>> While very unlikely to happen in practice, let's just enforce that
>> min_order_for_split() is called with the folio lock held. We can
>> significantly cleanup the calling hwpoison code, and just get rid
>> of try_to_split_thp_page() to hold the folio lock for a bit longer.
>>
>> Just work on folios now, which further cleans up the code. We just
>> have to be careful about doing the page_folio() after splitting, which
>> we have to do already either way. Do not change the way we split for
>> now, this needs more thought and should be done separately.
>>
>> Cleaning this up we fix another issue: in soft_offline_in_use_page(), we
>> would currently have leaked a folio reference.
> 
> Yikes...
> 
>>
>> In folio_split(), document and assert that we need the folio lock.
> 
> You mean min_order_for_split()?

Very right :)

> 
>> Drop the questionable VM_BUG_ON_PAGE(!page_count(p), p) check entirely.
>>
>> The folio->mapping problem was identified by Sashiko, and Li Youhong
>> reported it by sending a proposal fix.
>>
>> This likely does not really warrant CCing stable, but I expect little
>> conflicts when doing the backport, so let's just CC stable because of
>> the refcount leak.
>>
>> Reported-by: Li Youhong <liyouhong@kylinos.cn>
>> Closes: https://lore.kernel.org/r/20260804035828.2684059-1-dayou5941@163.com
>> Fixes: 689b8986776c ("mm/memory-failure: improve large block size folio handling")
>> Cc: stable@vger.kernel.org
>> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> 
> A typo above and a nit below about a comment but otherwise LGTM so:
> 
> Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> 
>> ---
>> v4 of the last fixing attempts:
>>
>> https://lore.kernel.org/r/20260806031958.677935-1-dayou5941@163.com
>> ---
>>  mm/huge_memory.c    |  4 ++++
>>  mm/memory-failure.c | 56 ++++++++++++++++++-----------------------------------
>>  2 files changed, 23 insertions(+), 37 deletions(-)
>>
>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>> index 9b1f3b24f7e0d..a00df56a68b57 100644
>> --- a/mm/huge_memory.c
>> +++ b/mm/huge_memory.c
>> @@ -4362,10 +4362,14 @@ int folio_split(struct folio *folio, unsigned int new_order,
>>   * If a file-backed folio is truncated, 0 will be returned. Any subsequent
>>   * split attempt should get -EBUSY from split checking code.
>>   *
>> + * Context: @folio must be locked.
>> + *
>>   * Return: @folio's minimum order for split
>>   */
>>  unsigned int min_order_for_split(struct folio *folio)
>>  {
>> +	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
>> +
>>  	if (folio_test_anon(folio))
>>  		return 0;
>>
>> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
>> index aaf14608b30e2..f4f3f1fc9eaff 100644
>> --- a/mm/memory-failure.c
>> +++ b/mm/memory-failure.c
>> @@ -1705,26 +1705,6 @@ static int identify_page_state(unsigned long pfn, struct page *p,
>>  	return page_action(ps, p, pfn);
>>  }
>>
>> -/*
>> - * When 'release' is 'false', it means that if thp split has failed,
>> - * there is still more to do, hence the page refcount we took earlier
>> - * is still needed.
>> - */
>> -static int try_to_split_thp_page(struct page *page, unsigned int new_order,
>> -		bool release)
>> -{
>> -	int ret;
>> -
>> -	lock_page(page);
>> -	ret = split_huge_page_to_order(page, new_order);
>> -	unlock_page(page);
>> -
>> -	if (ret && release)
>> -		put_page(page);
>> -
>> -	return ret;
>> -}
>> -
>>  static void unmap_and_kill(struct list_head *to_kill, unsigned long pfn,
>>  		struct address_space *mapping, pgoff_t index, int flags)
>>  {
>> @@ -2509,9 +2489,6 @@ int memory_failure(unsigned long pfn, int flags)
>>  	folio_unlock(folio);
>>
>>  	if (folio_test_large(folio)) {
>> -		const int new_order = min_order_for_split(folio);
>> -		int err;
>> -
>>  		/*
>>  		 * The flag must be set after the refcount is bumped
>>  		 * otherwise it may race with THP split.
>> @@ -2526,24 +2503,25 @@ int memory_failure(unsigned long pfn, int flags)
>>  		 * page is a valid handlable page.
>>  		 */
>>  		folio_set_has_hwpoisoned(folio);
>> -		err = try_to_split_thp_page(p, new_order, /* release= */ false);
>> +
>> +		folio_lock(folio);
>> +		split_huge_page_to_order(p, min_order_for_split(folio));
>> +		folio = page_folio(p);
>> +		folio_unlock(folio);
>> +
>>  		/*
>>  		 * If splitting a folio to order-0 fails, kill the process.
>>  		 * Split the folio regardless to minimize unusable pages.
>>  		 * Because the memory failure code cannot handle large
>>  		 * folios, this split is always treated as if it failed.
>>  		 */
>> -		if (err || new_order) {
>> -			/* get folio again in case the original one is split */
>> -			folio = page_folio(p);
>> +		if (folio_test_large(folio)) {
>>  			res = -EHWPOISON;
>>  			kill_procs_now(p, pfn, flags, folio);
>> -			put_page(p);
>> +			folio_put(folio);
>>  			action_result(pfn, MF_MSG_UNSPLIT_THP, MF_FAILED);
>>  			goto unlock_mutex;
>>  		}
>> -		VM_BUG_ON_PAGE(!page_count(p), p);
>> -		folio = page_folio(p);
>>  	}
>>
>>  	/*
>> @@ -2861,9 +2839,8 @@ static int soft_offline_in_use_page(struct page *page)
>>  		.reason = MR_MEMORY_FAILURE,
>>  	};
>>
>> +	folio_lock(folio);
>>  	if (!huge && folio_test_large(folio)) {
>> -		const int new_order = min_order_for_split(folio);
>> -
>>  		/*
>>  		 * If new_order (target split order) is not 0, do not split the
>>  		 * folio at all to retain the still accessible large folio.
>> @@ -2871,15 +2848,20 @@ static int soft_offline_in_use_page(struct page *page)
>>  		 * preferred, split it to non-zero new_order like it is done in
>>  		 * memory_failure().
>>  		 */
> 
> This comment is:
> 
> 		/*
> 		 * If new_order (target split order) is not 0, do not split the
> 		 * folio at all to retain the still accessible large folio.
> 		 * NOTE: if minimizing the number of soft offline pages is
> 		 * preferred, split it to non-zero new_order like it is done in
> 		 * memory_failure().
> 		 */
> 
> Which references 'new_order' twice - probably need to update this.

Agreed, thanks!

While at it, I'll just drop the NOTE that is not of any value, really?


diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index f4f3f1fc9eaff..9f1e547e7b472 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -2842,11 +2842,8 @@ static int soft_offline_in_use_page(struct page *page)
        folio_lock(folio);
        if (!huge && folio_test_large(folio)) {
                /*
-                * If new_order (target split order) is not 0, do not split the
-                * folio at all to retain the still accessible large folio.
-                * NOTE: if minimizing the number of soft offline pages is
-                * preferred, split it to non-zero new_order like it is done in
-                * memory_failure().
+                * If we cannot split to order-0, do not split the folio at all
+                * to retain the still accessible large folio.
                 */
                if (!min_order_for_split(folio))
                        ret = split_huge_page_to_order(page, 0) ? -EBUSY : 0;

> 
>> -		if (new_order || try_to_split_thp_page(page, /* new_order= */ 0,
>> -						       /* release= */ true)) {
>> +		if (!min_order_for_split(folio))
>> +			ret = split_huge_page_to_order(page, 0) ? -EBUSY : 0;
>> +		else
>> +			ret = -EBUSY;
>> +		folio = page_folio(page);
>> +
>> +		if (ret) {
>> +			folio_unlock(folio);
>> +			folio_put(folio);
> 
> Wow, incredible that we had a refcount leak here for ages and didn't know. Ugh
> what a mess this hw posion crap is!

I mean, soft-offline handling is not what runs on ... everyone's box. And the
memory will effectively be dead soon either way (hardware tells us that this
memory is likely going to get hwpoison soon) in most cases ... :)

-- 
Cheers,

David

  reply	other threads:[~2026-08-06 14:17 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-06 11:14 [PATCH] mm/memory-failure: fix folio refcount leak and min_order_for_split() locking David Hildenbrand (Arm)
2026-08-06 12:06 ` Usama Arif
2026-08-06 14:03 ` Lorenzo Stoakes (ARM)
2026-08-06 14:17   ` David Hildenbrand (Arm) [this message]
2026-08-06 14:26     ` Lorenzo Stoakes (ARM)
2026-08-06 14:55 ` Zi Yan
2026-08-07  1:49 ` Baolin Wang
2026-08-07  9:28 ` Miaohe Lin

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=d6e665cd-08a3-4598-8a4f-1af21eea4132@kernel.org \
    --to=david@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=baohua@kernel.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=dev.jain@arm.com \
    --cc=lance.yang@linux.dev \
    --cc=liam@infradead.org \
    --cc=linmiaohe@huawei.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=liyouhong@kylinos.cn \
    --cc=ljs@kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=nao.horiguchi@gmail.com \
    --cc=npache@redhat.com \
    --cc=richard.weiyang@gmail.com \
    --cc=ryan.roberts@arm.com \
    --cc=stable@vger.kernel.org \
    --cc=usama.arif@linux.dev \
    --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