Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm: kmemleak: scan modified objects as soon as they are grayed
@ 2026-10-16 16:07 Breno Leitao
  2026-10-19 15:56 ` Catalin Marinas
  0 siblings, 1 reply; 2+ messages in thread
From: Breno Leitao @ 2026-10-16 16:07 UTC (permalink / raw)
  To: Catalin Marinas, Andrew Morton, David Hildenbrand
  Cc: linux-mm, linux-kernel, kernel-team, Breno Leitao

kmemleak keeps reporting the guard page of a systemd-journald thread
stack on Meta's debug kernels, even with min_unref_scans set:

  unreferenced object 0xffff88829a30ae40 (size 256):
    comm "systemd-journal", pid 961, jiffies 4294814590
    backtrace (crc 49b3881c):
      kmem_cache_alloc_noprof+0x2ee/0x630
      vm_area_dup+0x36/0x750
      __split_vma+0xef/0x7d0
      vma_modify+0x11c3/0x18a0
      vma_modify_flags+0x1ea/0x2a0
      mprotect_fixup+0x249/0x630
      do_mprotect_pkey+0x531/0x820
      __x64_sys_mprotect+0x7c/0x90

The VMA is still mapped. It was never faulted in, so it has no anon_vma
and no file, and the only pointer to it is a slot in a maple tree leaf.
The mm tree runs in RCU mode: every write replaces the leaf with a copy
and frees the old one after a grace period. journald rewrites its tree
every couple of seconds, while a scan on a large debug host takes many
minutes, so the leaf kmemleak queued is freed before its turn, and the
copy that replaced it was linked after its parent had been scanned.

The pass over modified objects is meant to catch such moved references,
and the new leaf, being a new white object, does get grayed there.

But the gray list is only scanned after the whole object_list walk, and
by then that copy has been replaced and freed as well. The confirmation
scan has no such pass, so the miss repeats round after round no matter
how many consecutive scans are required.

Scan the gray list as soon as an object is grayed, from
kmemleak_cond_resched(), which already pins the current object while the
RCU read lock is dropped. The live leaf is then read right after the
walk reaches it, before it can be freed.

I was able to come up with a reproducer that reproduce these false
positives most of the time:

https://github.com/leitao/linux/commit/1291579c487303c5ffbea2cf532649d26329c8a9

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 mm/kmemleak.c | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/mm/kmemleak.c b/mm/kmemleak.c
index 8fa409a4f9fb26..6e12bcd95312d2 100644
--- a/mm/kmemleak.c
+++ b/mm/kmemleak.c
@@ -1696,8 +1696,10 @@ static void scan_gray_list(void)
  * Conditionally call resched() in an object iteration loop while making sure
  * that the given object won't go away without RCU read lock by performing a
  * get_object() if necessaary.
+ * If @scan_gray is set, also scan the gray list while the RCU lock is dropped.
  */
-static void kmemleak_cond_resched(struct kmemleak_object *object)
+static void kmemleak_cond_resched(struct kmemleak_object *object,
+				  bool scan_gray)
 {
 	if (!get_object(object))
 		return;	/* Try next object */
@@ -1709,6 +1711,8 @@ static void kmemleak_cond_resched(struct kmemleak_object *object)
 	raw_spin_unlock_irq(&kmemleak_lock);
 
 	rcu_read_unlock();
+	if (scan_gray)
+		scan_gray_list();
 	cond_resched_tasks_rcu_qs();
 	rcu_read_lock();
 
@@ -1910,7 +1914,7 @@ static int __kmemleak_scan(bool full)
 		raw_spin_unlock_irq(&object->lock);
 
 		if (need_resched())
-			kmemleak_cond_resched(object);
+			kmemleak_cond_resched(object, false);
 	}
 	rcu_read_unlock();
 
@@ -1981,8 +1985,13 @@ static int __kmemleak_scan(bool full)
 	 */
 	rcu_read_lock();
 	list_for_each_entry_rcu(object, &object_list, object_list) {
-		if (need_resched())
-			kmemleak_cond_resched(object);
+		/*
+		 * Scan newly grayed objects now, not after the walk: one may
+		 * hold the only reference to another and be freed by then,
+		 * like a maple tree node that keeps being replaced.
+		 */
+		if (need_resched() || !list_empty(&gray_list))
+			kmemleak_cond_resched(object, true);
 
 		/*
 		 * This is racy but we can save the overhead of lock/unlock
@@ -2082,7 +2091,7 @@ static void kmemleak_scan(void)
 		bool dedup_print;
 
 		if (need_resched())
-			kmemleak_cond_resched(object);
+			kmemleak_cond_resched(object, false);
 
 		/*
 		 * This is racy but we can save the overhead of lock/unlock

---
base-commit: 587858367581b9c55c3690f4e63382ad622719d4
change-id: 20261015-kmemleak-gray-drain-fd80160fd946

Best regards,
--  
Breno Leitao <leitao@debian.org>



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

* Re: [PATCH] mm: kmemleak: scan modified objects as soon as they are grayed
  2026-10-16 16:07 [PATCH] mm: kmemleak: scan modified objects as soon as they are grayed Breno Leitao
@ 2026-10-19 15:56 ` Catalin Marinas
  0 siblings, 0 replies; 2+ messages in thread
From: Catalin Marinas @ 2026-10-19 15:56 UTC (permalink / raw)
  To: Breno Leitao
  Cc: Andrew Morton, David Hildenbrand, linux-mm, linux-kernel,
	kernel-team

On Fri, Oct 16, 2026 at 09:07:24AM -0700, Breno Leitao wrote:
> diff --git a/mm/kmemleak.c b/mm/kmemleak.c
> index 8fa409a4f9fb26..6e12bcd95312d2 100644
> --- a/mm/kmemleak.c
> +++ b/mm/kmemleak.c
> @@ -1696,8 +1696,10 @@ static void scan_gray_list(void)
>   * Conditionally call resched() in an object iteration loop while making sure
>   * that the given object won't go away without RCU read lock by performing a
>   * get_object() if necessaary.
> + * If @scan_gray is set, also scan the gray list while the RCU lock is dropped.
>   */
> -static void kmemleak_cond_resched(struct kmemleak_object *object)
> +static void kmemleak_cond_resched(struct kmemleak_object *object,
> +				  bool scan_gray)
>  {
>  	if (!get_object(object))
>  		return;	/* Try next object */
> @@ -1709,6 +1711,8 @@ static void kmemleak_cond_resched(struct kmemleak_object *object)
>  	raw_spin_unlock_irq(&kmemleak_lock);
>  
>  	rcu_read_unlock();
> +	if (scan_gray)
> +		scan_gray_list();
>  	cond_resched_tasks_rcu_qs();
>  	rcu_read_lock();
>  
> @@ -1910,7 +1914,7 @@ static int __kmemleak_scan(bool full)
>  		raw_spin_unlock_irq(&object->lock);
>  
>  		if (need_resched())
> -			kmemleak_cond_resched(object);
> +			kmemleak_cond_resched(object, false);
>  	}
>  	rcu_read_unlock();
>  
> @@ -1981,8 +1985,13 @@ static int __kmemleak_scan(bool full)
>  	 */
>  	rcu_read_lock();
>  	list_for_each_entry_rcu(object, &object_list, object_list) {
> -		if (need_resched())
> -			kmemleak_cond_resched(object);
> +		/*
> +		 * Scan newly grayed objects now, not after the walk: one may
> +		 * hold the only reference to another and be freed by then,
> +		 * like a maple tree node that keeps being replaced.
> +		 */
> +		if (need_resched() || !list_empty(&gray_list))
> +			kmemleak_cond_resched(object, true);

I think that's a good way to reduce the false positives for such maple
tree scenario. Do you have any numbers on how much this extends the scan
time by? If it's too expensive, we could rate-limit it, say every jiffy.

>  
>  		/*
>  		 * This is racy but we can save the overhead of lock/unlock
> @@ -2082,7 +2091,7 @@ static void kmemleak_scan(void)
>  		bool dedup_print;
>  
>  		if (need_resched())
> -			kmemleak_cond_resched(object);
> +			kmemleak_cond_resched(object, false);

Nitpick: instead of passing 'false' in a few places, rename
kmemleak_cond_resched() to kmemleak_resched_drain() (well, some better
name) call get kmemleak_cond_resched() to call that with 'false'. We
could also move need_resched() in such function, being a small wrapper
the compiler should inline it.

Either way,

Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>


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

end of thread, other threads:[~2026-10-19 15:57 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 16:07 [PATCH] mm: kmemleak: scan modified objects as soon as they are grayed Breno Leitao
2026-10-19 15:56 ` Catalin Marinas

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