The kmemleak marking phase is not atomic. While the object graph is traversed, the kernel can modify pointers, free objects or allocate new ones. If a reference to an object is moved from one location to another, kmemleak scanning may miss it. We have explicit annotations like kmemleak_transient_leak() but identifying and maintaining them is not trivial. Given that such transient leaks are short-lived, rather than just reporting such objects as leaks, do another scan to confirm the suspected objects. If no new leaks are found during the first scan, skip the confirmation one. Signed-off-by: Catalin Marinas Assisted-by: Claude:claude-opus-4-8 Cc: Breno Leitao Cc: Andrew Morton --- As discussed here: https://lore.kernel.org/r/ako1sMPVu4dfQR4K@casper.infradead.org/ attempting some more heuristics to reduce the transient leaks. It overlaps a bit with Breno's minimum scans patchset but this one limits it to two scans only back to back and only if new leaks are found. I would say it is complementary. The difference from the link above is that we now use the same scan loop to mark the suspected leaks instead of a separate, heavier one. I told claude to refactor kmemleak_scan(), hence the assisted-by above. Ideas mine ;). BTW, I'll be away from Saturday for three weeks. I may reply occasionally but won't be able to test anything. Thanks. mm/kmemleak.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index 7c7ba17ce7af..505b5c3a423b 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -175,6 +175,8 @@ struct kmemleak_object { #define OBJECT_PHYS (1 << 4) /* flag set for per-CPU pointers */ #define OBJECT_PERCPU (1 << 5) +/* flag set on an object left unreferenced by the full scan, pending confirmation */ +#define OBJECT_SUSPECT (1 << 6) /* set when __remove_object() called */ #define DELSTATE_REMOVED (1 << 0) @@ -235,6 +237,8 @@ static unsigned long jiffies_min_age; static unsigned long jiffies_last_scan; /* delay between automatic memory scannings */ static unsigned long jiffies_scan_wait; +/* number of objects flagged OBJECT_SUSPECT during the current scan */ +static int nr_suspects; /* enables or disables the task stacks scanning */ static int kmemleak_stack_scan = 1; /* protects the memory scanning, parameters and debug/kmemleak file access */ @@ -1439,6 +1443,11 @@ static void update_refs(struct kmemleak_object *object) */ object->count++; if (color_gray(object)) { + /* referenced after all, no longer a suspect */ + if (object->flags & OBJECT_SUSPECT) { + object->flags &= ~OBJECT_SUSPECT; + nr_suspects--; + } /* put_object() called when removing from gray_list */ WARN_ON(!get_object(object)); list_add_tail(&object->gray_list, &gray_list); @@ -1797,15 +1806,15 @@ static void dedup_flush(struct xarray *dedup) * kernel's standard allocators. This function must be called with the * scan_mutex held. */ -static void kmemleak_scan(void) +static int __kmemleak_scan(bool full) { struct kmemleak_object *object; struct zone *zone; int __maybe_unused i; - struct xarray dedup; - int new_leaks = 0; jiffies_last_scan = jiffies; + if (full) + nr_suspects = 0; /* prepare the kmemleak_object's */ rcu_read_lock(); @@ -1835,6 +1844,8 @@ static void kmemleak_scan(void) /* reset the reference count (whiten the object) */ object->count = 0; + if (full) + object->flags &= ~OBJECT_SUSPECT; if (color_gray(object) && get_object(object)) list_add_tail(&object->gray_list, &gray_list); @@ -1904,6 +1915,10 @@ static void kmemleak_scan(void) */ scan_gray_list(); + /* a confirmation scan does not look for modified objects */ + if (!full) + return nr_suspects; + /* * Check for new or unreferenced objects modified since the previous * scan and color them gray until the next scan. @@ -1926,6 +1941,11 @@ static void kmemleak_scan(void) /* color it gray temporarily */ object->count = object->min_count; list_add_tail(&object->gray_list, &gray_list); + } else if (unreferenced_object(object) && + !(object->flags & OBJECT_REPORTED)) { + /* flag the objects left unreferenced by this scan */ + object->flags |= OBJECT_SUSPECT; + nr_suspects++; } raw_spin_unlock_irq(&object->lock); } @@ -1936,12 +1956,42 @@ static void kmemleak_scan(void) */ scan_gray_list(); + return nr_suspects; +} + +/* + * Scan the memory and report the unreferenced objects as leaks. Must be + * called with the scan_mutex held. + */ +static void kmemleak_scan(void) +{ + struct kmemleak_object *object; + struct xarray dedup; + int new_leaks = 0; + + /* + * Full scan. Objects left unreferenced are flagged OBJECT_SUSPECT and + * counted in the return value; nothing to confirm or report otherwise. + */ + if (!__kmemleak_scan(true)) + return; + /* * If scanning was stopped do not report any new unreferenced objects. */ if (scan_should_stop()) return; + /* + * A live object whose only reference is moved by, for example, a + * concurrent RCU update can be missed for one scan and reported as a + * transient false positive. Scan again and only report the objects + * left unreferenced (still flagged OBJECT_SUSPECT) by both scans. + */ + __kmemleak_scan(false); + if (scan_should_stop()) + return; + /* * Scanning result reporting. When verbose printing is enabled, dedupe * by stackdepot trace_handle so each unique backtrace is logged once @@ -1969,6 +2019,7 @@ static void kmemleak_scan(void) trace_handle = 0; dedup_print = false; if (unreferenced_object(object) && + (object->flags & OBJECT_SUSPECT) && !(object->flags & OBJECT_REPORTED)) { object->flags |= OBJECT_REPORTED; if (kmemleak_verbose) {