mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] mm: kmemleak: scan modified objects as soon as they are grayed
@ 2026-10-30 18:31 Breno Leitao
  2026-10-30 19:06 ` Andrew Morton
  0 siblings, 1 reply; 2+ messages in thread
From: Breno Leitao @ 2026-10-30 18:31 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 during the walk instead, within a jiffy of an object
being grayed. Do it from a variant of kmemleak_cond_resched(), which
already pins the current object while the RCU read lock is dropped. The
live leaf is then read shortly after the walk reaches it, before it can
be freed.

Scanning the gray list after every grayed object made a full scan 41%
slower (1.72s -> 2.42s, arm64 guest with lockdep) when the pass grays a
million objects that do not point to each other. Scanning it at most
once a jiffy keeps that at the baseline, leaves idle, file storm and
mmap churn workloads unchanged, and still removes the false positives
at HZ=100.

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

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

Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Changes in v2:
- Keep kmemleak_cond_resched() and its callers as they are. Pass 2 calls
  a new kmemleak_resched_drain() helper, which decides when to scan the
  gray list and calls the new __kmemleak_cond_resched() (Catalin). The
  other two callers keep need_resched() and are not touched.
- Scan the gray list at most once a jiffy (Catalin). Doing it after every
  grayed object cost up to 41% of the scan time in a synthetic worst
  case. This part can be dropped if it is not wanted.
- Add the scan time numbers to the commit message.
- Keep Catalin's Reviewed-by, as both changes follow his review.
- Link to v1: https://patch.msgid.link/20261016-kmemleak-gray-drain-v1-1-3270256fdc8e@debian.org

Tested on an arm64 virtme-ng guest (8 vCPUs, lockdep, HZ=1000,
min_unref_scans=1), two boots of ten scans per kernel, same base:
- Guard VMA reproducer: base reported live guard VMAs as leaks in 19
  of 20 scans (up to 106 at once) and needed a confirmation pass in 17.
  v2 reported none and never needed one, so a scan took 13.6s instead
  of 26.4s.
- Pass 2 graying 1M objects that do not point to each other: base
  1.74s, v1 2.46s, v2 1.73s per scan.
- No lockdep, RCU or WARN splats in any run.

The v2 changes, the changelog update and the measurements above were
done with Claude Code (claude-opus-5-5).
---
 mm/kmemleak.c | 31 ++++++++++++++++++++++++++++---
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/mm/kmemleak.c b/mm/kmemleak.c
index 8fa409a4f9fb26..7e24408ba83821 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();
 
@@ -1721,6 +1725,26 @@ static void kmemleak_cond_resched(struct kmemleak_object *object)
 	put_object(object);
 }
 
+static void kmemleak_cond_resched(struct kmemleak_object *object)
+{
+	__kmemleak_cond_resched(object, false);
+}
+
+/*
+ * Scan newly grayed objects within a jiffy, not after the object_list walk:
+ * one may hold the only reference to another and be freed by then, like a
+ * maple tree node being replaced.
+ */
+static void kmemleak_resched_drain(struct kmemleak_object *object,
+				   unsigned long *last_drain)
+{
+	if (need_resched() ||
+	    (!list_empty(&gray_list) && time_after(jiffies, *last_drain))) {
+		__kmemleak_cond_resched(object, true);
+		*last_drain = jiffies;
+	}
+}
+
 /*
  * Scan all task kernel stacks, rescheduling between tasks. Each task is looked
  * up and pinned within its own RCU read-side section, so no lock is held across
@@ -1863,6 +1887,7 @@ static void dedup_flush(struct xarray *dedup)
 static int __kmemleak_scan(bool full)
 {
 	struct kmemleak_object *object;
+	unsigned long last_drain;
 	struct zone *zone;
 	int __maybe_unused i;
 	int stop = 0;
@@ -1979,10 +2004,10 @@ static int __kmemleak_scan(bool full)
 	 * Check for new or unreferenced objects modified since the previous
 	 * scan and color them gray until the next scan.
 	 */
+	last_drain = jiffies;
 	rcu_read_lock();
 	list_for_each_entry_rcu(object, &object_list, object_list) {
-		if (need_resched())
-			kmemleak_cond_resched(object);
+		kmemleak_resched_drain(object, &last_drain);
 
 		/*
 		 * 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	[flat|nested] 2+ messages in thread

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

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 18:31 [PATCH v2] mm: kmemleak: scan modified objects as soon as they are grayed Breno Leitao
2026-10-30 19:06 ` Andrew Morton

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

all inboxes | Powered by JetHome®