* [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
* Re: [PATCH v2] mm: kmemleak: scan modified objects as soon as they are grayed
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
0 siblings, 0 replies; 2+ messages in thread
From: Andrew Morton @ 2026-10-30 19:06 UTC (permalink / raw)
To: Breno Leitao
Cc: Catalin Marinas, David Hildenbrand, linux-mm, linux-kernel, kernel-team
On Fri, 30 Oct 2026 11:31:10 -0700 Breno Leitao <leitao@debian.org> wrote:
> 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.
Thanks. I'm thinking
Fixes: 04609ccc40c4 ("kmemleak: Reduce the false positives by checking for modified objects")
cc:stable
Agreed?
^ 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®