Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm/huge_memory: avoid TOCTOU race in min_order_for_split()
@ 2026-08-04  3:58 dayou5941
       [not found] ` <9ac32dd4-498e-46b2-9cb4-3fa853840cfa@kernel.org>
       [not found] ` <anG6-Dm5oo4LJxRx@lucifer>
  0 siblings, 2 replies; 3+ messages in thread
From: dayou5941 @ 2026-08-04  3:58 UTC (permalink / raw)
  To: akpm, david, ljs; +Cc: ziy, linux-mm, liyouhong

From: liyouhong <liyouhong@kylinos.cn>

min_order_for_split() reads folio->mapping twice without any
synchronization. Concurrent truncate or invalidate can clear
folio->mapping between the check and subsequent function call.
Even with a held folio reference preventing the folio from
being freed, folio->mapping can still be overwritten to NULL. This
TOCTOU race allows passing a NULL mapping into mapping_min_folio_order(),
which leads to a NULL pointer dereference.

Cache folio->mapping to a local variable using READ_ONCE() to guarantee a
single memory load and remove the race window.

Link: https://sashiko.dev/#/patchset/20260803060001.800638-1-dayou5941@163.com
Signed-off-by: liyouhong <liyouhong@kylinos.cn>
---
 mm/huge_memory.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2bccb0a53a0a..54d4261a2a15 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4284,19 +4284,23 @@ int folio_split(struct folio *folio, unsigned int new_order,
  */
 unsigned int min_order_for_split(struct folio *folio)
 {
+	struct address_space *mapping;
+
 	if (folio_test_anon(folio))
 		return 0;
 
+	mapping = READ_ONCE(folio->mapping);
+
 	/*
 	 * If the folio got truncated, we don't know the previous mapping and
 	 * consequently the old min order. But it doesn't matter, as any split
 	 * attempt will immediately fail with -EBUSY as the folio cannot get
 	 * split until freed.
 	 */
-	if (!folio->mapping)
+	if (!mapping)
 		return 0;
 
-	return mapping_min_folio_order(folio->mapping);
+	return mapping_min_folio_order(mapping);
 }
 
 int split_folio_to_list(struct folio *folio, struct list_head *list)
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re:Re: [PATCH] mm/huge_memory: avoid TOCTOU race in min_order_for_split()
       [not found] ` <9ac32dd4-498e-46b2-9cb4-3fa853840cfa@kernel.org>
@ 2026-08-05  7:20   ` 李佑鸿 
  0 siblings, 0 replies; 3+ messages in thread
From: 李佑鸿  @ 2026-08-05  7:20 UTC (permalink / raw)
  To: David Hildenbrand (Arm); +Cc: akpm, ljs, ziy, linux-mm, liyouhong


















At 2026-08-04 18:04:15, "David Hildenbrand (Arm)" <david@kernel.org> wrote:
>On 8/4/26 05:58, dayou5941@163.com wrote:
>> From: liyouhong <liyouhong@kylinos.cn>
>> 
>> min_order_for_split() reads folio->mapping twice without any
>> synchronization. Concurrent truncate or invalidate can clear
>> folio->mapping between the check and subsequent function call.
>> Even with a held folio reference preventing the folio from
>> being freed, folio->mapping can still be overwritten to NULL. This
>> TOCTOU race allows passing a NULL mapping into mapping_min_folio_order(),
>> which leads to a NULL pointer dereference.
>> 
>> Cache folio->mapping to a local variable using READ_ONCE() to guarantee a
>> single memory load and remove the race window.
>
>In __folio_split() we hold the folio lock, so concurrent truncation is impossible.
>
>So is this about memory-failure.c ?
>
>We should rather fix that code to not call min_order_for_split() without the folio lock

>(we take it either way in try_to_split_thp_page), no?


Yes, you're right. The real issue is in memory-failure.c where the callers
do not hold the folio lock when calling min_order_for_split(). I'll send a
v2 that adds the folio lock at the call sites and also adds a
VM_WARN_ON_ONCE_FOLIO() in min_order_for_split() to catch any future
callers that don't hold the lock. Thanks for the review.

Cheers,
liyouhong



^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re:Re: [PATCH] mm/huge_memory: avoid TOCTOU race in min_order_for_split()
       [not found] ` <anG6-Dm5oo4LJxRx@lucifer>
@ 2026-08-05  7:23   ` 李佑鸿 
  0 siblings, 0 replies; 3+ messages in thread
From: 李佑鸿  @ 2026-08-05  7:23 UTC (permalink / raw)
  To: Lorenzo Stoakes (ARM); +Cc: akpm, david, ziy, linux-mm, liyouhong


















At 2026-08-04 18:34:28, "Lorenzo Stoakes (ARM)" <ljs@kernel.org> wrote:
>On Tue, Aug 04, 2026 at 11:58:28AM +0800, dayou5941@163.com wrote:
>> From: liyouhong <liyouhong@kylinos.cn>
>>
>> min_order_for_split() reads folio->mapping twice without any
>
>Actually 3 times, since folio_test_anon() also references folio->mapping.
>
>But in reality, it's once not twice or 3 times, as the compiler just folds
>it all.
>
>So I don't think there's a race here at all with a NULL pointer deref?
>
>	<hoist folio->mapping to register>
>
>	if (folio_test_anon(folio)) -> inlined -> folio->mapping & FOLIO_MAPPING_ANON [ safe no null deref anyway ]
>		return 0;
>
>	/*
>	 * If the folio got truncated, we don't know the previous mapping and
>	 * consequently the old min order. But it doesn't matter, as any split
>	 * attempt will immediately fail with -EBUSY as the folio cannot get
>	 * split until freed.
>	 */
>	if (!folio->mapping) -> hoisted folio->mapping value checked
>		return 0;
>
>	return mapping_min_folio_order(folio->mapping); -> 	mapping->flags & AS_FOLIO_ORDER_MIN_MASK >> AS_FOLIO_ORDER_MIN;
>
>You do however have an issue potentially with a torn read I think?
>
>> synchronization. Concurrent truncate or invalidate can clear
>> folio->mapping between the check and subsequent function call.
>
>I mean David gets at this, but can this actually happen where this is
>called from?
>
>This is a local function.
>
>> Even with a held folio reference preventing the folio from
>> being freed, folio->mapping can still be overwritten to NULL. This
>> TOCTOU race allows passing a NULL mapping into mapping_min_folio_order(),
>> which leads to a NULL pointer dereference.
>
>Theoretical. You're not reporting a bug, you're reporting AI output. Be
>clear about that please.
>
>>
>> Cache folio->mapping to a local variable using READ_ONCE() to guarantee a
>> single memory load and remove the race window.
>
>It's already cached, and it doesn't guarantee a single memory load since
>your code as-is loads it twice via folio_test_anon().
>
>You see, this is workslopping here - you've not taken the time to carefully
>check this - and now David and I are having to point things out, and you're
>acting on behalf of an AI agent.
>
>Please in future _properly investigate the claim_. Do NOT assume Sashiko is
>right.
>
>This fix is not in the right place.
>
>>
>> Link: https://sashiko.dev/#/patchset/20260803060001.800638-1-dayou5941@163.com
>
>Don't just take sashiko suggestions as reality. I've found it gives 50% noise,
>50% signal.
>
>Also doubly don't do this for 'this is not this patch but' stuff.
>
>It's worth investigating and _reproducing_ if possible, or even examining
>generated assembly.
>
>We already have enough work as it is without having to by-proxy check to
>see if Sashiko is hallucinating :)
>
>And in any case, go and _look at the callers_.  Having reviews have to do
>this for you is workslopping-by-proxy and isn't OK.
>
>> Signed-off-by: liyouhong <liyouhong@kylinos.cn>
>
>You're also missing Fixes & Cc: stable tags.
>
>> ---
>>  mm/huge_memory.c | 8 ++++++--
>>  1 file changed, 6 insertions(+), 2 deletions(-)
>>
>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>> index 2bccb0a53a0a..54d4261a2a15 100644
>> --- a/mm/huge_memory.c
>> +++ b/mm/huge_memory.c
>> @@ -4284,19 +4284,23 @@ int folio_split(struct folio *folio, unsigned int new_order,
>>   */
>>  unsigned int min_order_for_split(struct folio *folio)
>>  {
>> +	struct address_space *mapping;
>> +
>>  	if (folio_test_anon(folio))
>>  		return 0;
>
>Read folio->mapping.
>
>>
>> +	mapping = READ_ONCE(folio->mapping);
>
>Oh, read it again.
>
>This isn't great, and you can't hoist the mapping above because then you
>can't use folio_test_anon()...
>
>That should have been a clue that maybe this wasn't the right place to
>'fix' this 'bug' :)
>
>> +
>>  	/*
>>  	 * If the folio got truncated, we don't know the previous mapping and
>>  	 * consequently the old min order. But it doesn't matter, as any split
>>  	 * attempt will immediately fail with -EBUSY as the folio cannot get
>>  	 * split until freed.
>>  	 */
>> -	if (!folio->mapping)
>> +	if (!mapping)
>>  		return 0;
>>
>> -	return mapping_min_folio_order(folio->mapping);
>> +	return mapping_min_folio_order(mapping);
>>  }
>>
>>  int split_folio_to_list(struct folio *folio, struct list_head *list)
>> --
>> 2.25.1
>>
>
>The callers are memory_failure() and
>soft_offline_in_use_page(). memory_failure() drops a folio lock before
>doing this (!). soft_offline_in_use_page() seems to do this without even
>bothering with a folio lock.
>
>So as David said, the bug's there and needs proper investigation. This
>patch ain't it.
>
>P.S. I really think we need a way to disable this 'not an issue in your
>series but' stuff from sashiko, it's a huge distraction. If sashiko wants
>to do passive monitoring of existing issues, it should do it properly and
>separately, not adding more to unmanageable reviewer workload.
>

Thanks for the detailed analysis. You and David are both correct — the
compiler folds the reads so there's no real TOCTOU race inside
min_order_for_split() itself. The actual problem is the callers in
memory-failure.c not holding the folio lock. I'll send a v2 with:

1. VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio) in
   min_order_for_split().
2. Taking the folio lock around the min_order_for_split() calls in both
   memory_failure() and soft_offline_in_use_page().

Appreciate the thorough review, and apologies for the noise from the
initial AI-generated report — I'll make sure to properly investigate
before posting in the future.

Cheers,
liyouhong

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-05  7:24 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04  3:58 [PATCH] mm/huge_memory: avoid TOCTOU race in min_order_for_split() dayou5941
     [not found] ` <9ac32dd4-498e-46b2-9cb4-3fa853840cfa@kernel.org>
2026-08-05  7:20   ` 李佑鸿 
     [not found] ` <anG6-Dm5oo4LJxRx@lucifer>
2026-08-05  7:23   ` 李佑鸿 

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox