Commit 04609ccc authored by Catalin Marinas's avatar Catalin Marinas

kmemleak: Reduce the false positives by checking for modified objects

If an object was modified since it was previously suspected as leak, do
not report it. The modification check is done by calculating the
checksum (CRC32) of such object.

Several false positives are caused by objects being removed from linked
lists (e.g. allocation pools) and temporarily breaking the reference
chain since kmemleak runs concurrently with such list mutation
primitives.
Signed-off-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
parent fefdd336
...@@ -93,6 +93,7 @@ ...@@ -93,6 +93,7 @@
#include <linux/nodemask.h> #include <linux/nodemask.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/crc32.h>
#include <asm/sections.h> #include <asm/sections.h>
#include <asm/processor.h> #include <asm/processor.h>
...@@ -108,7 +109,6 @@ ...@@ -108,7 +109,6 @@
#define MSECS_MIN_AGE 5000 /* minimum object age for reporting */ #define MSECS_MIN_AGE 5000 /* minimum object age for reporting */
#define SECS_FIRST_SCAN 60 /* delay before the first scan */ #define SECS_FIRST_SCAN 60 /* delay before the first scan */
#define SECS_SCAN_WAIT 600 /* subsequent auto scanning delay */ #define SECS_SCAN_WAIT 600 /* subsequent auto scanning delay */
#define GRAY_LIST_PASSES 25 /* maximum number of gray list scans */
#define MAX_SCAN_SIZE 4096 /* maximum size of a scanned block */ #define MAX_SCAN_SIZE 4096 /* maximum size of a scanned block */
#define BYTES_PER_POINTER sizeof(void *) #define BYTES_PER_POINTER sizeof(void *)
...@@ -149,6 +149,8 @@ struct kmemleak_object { ...@@ -149,6 +149,8 @@ struct kmemleak_object {
int min_count; int min_count;
/* the total number of pointers found pointing to this object */ /* the total number of pointers found pointing to this object */
int count; int count;
/* checksum for detecting modified objects */
u32 checksum;
/* memory ranges to be scanned inside an object (empty for all) */ /* memory ranges to be scanned inside an object (empty for all) */
struct hlist_head area_list; struct hlist_head area_list;
unsigned long trace[MAX_TRACE]; unsigned long trace[MAX_TRACE];
...@@ -164,8 +166,6 @@ struct kmemleak_object { ...@@ -164,8 +166,6 @@ struct kmemleak_object {
#define OBJECT_REPORTED (1 << 1) #define OBJECT_REPORTED (1 << 1)
/* flag set to not scan the object */ /* flag set to not scan the object */
#define OBJECT_NO_SCAN (1 << 2) #define OBJECT_NO_SCAN (1 << 2)
/* flag set on newly allocated objects */
#define OBJECT_NEW (1 << 3)
/* number of bytes to print per line; must be 16 or 32 */ /* number of bytes to print per line; must be 16 or 32 */
#define HEX_ROW_SIZE 16 #define HEX_ROW_SIZE 16
...@@ -321,11 +321,6 @@ static bool color_gray(const struct kmemleak_object *object) ...@@ -321,11 +321,6 @@ static bool color_gray(const struct kmemleak_object *object)
object->count >= object->min_count; object->count >= object->min_count;
} }
static bool color_black(const struct kmemleak_object *object)
{
return object->min_count == KMEMLEAK_BLACK;
}
/* /*
* Objects are considered unreferenced only if their color is white, they have * Objects are considered unreferenced only if their color is white, they have
* not be deleted and have a minimum age to avoid false positives caused by * not be deleted and have a minimum age to avoid false positives caused by
...@@ -333,7 +328,7 @@ static bool color_black(const struct kmemleak_object *object) ...@@ -333,7 +328,7 @@ static bool color_black(const struct kmemleak_object *object)
*/ */
static bool unreferenced_object(struct kmemleak_object *object) static bool unreferenced_object(struct kmemleak_object *object)
{ {
return (object->flags & OBJECT_ALLOCATED) && color_white(object) && return (color_white(object) && object->flags & OBJECT_ALLOCATED) &&
time_before_eq(object->jiffies + jiffies_min_age, time_before_eq(object->jiffies + jiffies_min_age,
jiffies_last_scan); jiffies_last_scan);
} }
...@@ -381,6 +376,7 @@ static void dump_object_info(struct kmemleak_object *object) ...@@ -381,6 +376,7 @@ static void dump_object_info(struct kmemleak_object *object)
pr_notice(" min_count = %d\n", object->min_count); pr_notice(" min_count = %d\n", object->min_count);
pr_notice(" count = %d\n", object->count); pr_notice(" count = %d\n", object->count);
pr_notice(" flags = 0x%lx\n", object->flags); pr_notice(" flags = 0x%lx\n", object->flags);
pr_notice(" checksum = %d\n", object->checksum);
pr_notice(" backtrace:\n"); pr_notice(" backtrace:\n");
print_stack_trace(&trace, 4); print_stack_trace(&trace, 4);
} }
...@@ -522,12 +518,13 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size, ...@@ -522,12 +518,13 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size,
INIT_HLIST_HEAD(&object->area_list); INIT_HLIST_HEAD(&object->area_list);
spin_lock_init(&object->lock); spin_lock_init(&object->lock);
atomic_set(&object->use_count, 1); atomic_set(&object->use_count, 1);
object->flags = OBJECT_ALLOCATED | OBJECT_NEW; object->flags = OBJECT_ALLOCATED;
object->pointer = ptr; object->pointer = ptr;
object->size = size; object->size = size;
object->min_count = min_count; object->min_count = min_count;
object->count = -1; /* no color initially */ object->count = 0; /* white color initially */
object->jiffies = jiffies; object->jiffies = jiffies;
object->checksum = 0;
/* task information */ /* task information */
if (in_irq()) { if (in_irq()) {
...@@ -948,6 +945,20 @@ void __ref kmemleak_no_scan(const void *ptr) ...@@ -948,6 +945,20 @@ void __ref kmemleak_no_scan(const void *ptr)
} }
EXPORT_SYMBOL(kmemleak_no_scan); EXPORT_SYMBOL(kmemleak_no_scan);
/*
* Update an object's checksum and return true if it was modified.
*/
static bool update_checksum(struct kmemleak_object *object)
{
u32 old_csum = object->checksum;
if (!kmemcheck_is_obj_initialized(object->pointer, object->size))
return false;
object->checksum = crc32(0, (void *)object->pointer, object->size);
return object->checksum != old_csum;
}
/* /*
* Memory scanning is a long process and it needs to be interruptable. This * Memory scanning is a long process and it needs to be interruptable. This
* function checks whether such interrupt condition occured. * function checks whether such interrupt condition occured.
...@@ -1081,6 +1092,39 @@ out: ...@@ -1081,6 +1092,39 @@ out:
spin_unlock_irqrestore(&object->lock, flags); spin_unlock_irqrestore(&object->lock, flags);
} }
/*
* Scan the objects already referenced (gray objects). More objects will be
* referenced and, if there are no memory leaks, all the objects are scanned.
*/
static void scan_gray_list(void)
{
struct kmemleak_object *object, *tmp;
/*
* The list traversal is safe for both tail additions and removals
* from inside the loop. The kmemleak objects cannot be freed from
* outside the loop because their use_count was incremented.
*/
object = list_entry(gray_list.next, typeof(*object), gray_list);
while (&object->gray_list != &gray_list) {
cond_resched();
/* may add new objects to the list */
if (!scan_should_stop())
scan_object(object);
tmp = list_entry(object->gray_list.next, typeof(*object),
gray_list);
/* remove the object from the list and release it */
list_del(&object->gray_list);
put_object(object);
object = tmp;
}
WARN_ON(!list_empty(&gray_list));
}
/* /*
* Scan data sections and all the referenced memory blocks allocated via the * Scan data sections and all the referenced memory blocks allocated via the
* kernel's standard allocators. This function must be called with the * kernel's standard allocators. This function must be called with the
...@@ -1089,10 +1133,9 @@ out: ...@@ -1089,10 +1133,9 @@ out:
static void kmemleak_scan(void) static void kmemleak_scan(void)
{ {
unsigned long flags; unsigned long flags;
struct kmemleak_object *object, *tmp; struct kmemleak_object *object;
int i; int i;
int new_leaks = 0; int new_leaks = 0;
int gray_list_pass = 0;
jiffies_last_scan = jiffies; jiffies_last_scan = jiffies;
...@@ -1113,7 +1156,6 @@ static void kmemleak_scan(void) ...@@ -1113,7 +1156,6 @@ static void kmemleak_scan(void)
#endif #endif
/* reset the reference count (whiten the object) */ /* reset the reference count (whiten the object) */
object->count = 0; object->count = 0;
object->flags &= ~OBJECT_NEW;
if (color_gray(object) && get_object(object)) if (color_gray(object) && get_object(object))
list_add_tail(&object->gray_list, &gray_list); list_add_tail(&object->gray_list, &gray_list);
...@@ -1171,62 +1213,36 @@ static void kmemleak_scan(void) ...@@ -1171,62 +1213,36 @@ static void kmemleak_scan(void)
/* /*
* Scan the objects already referenced from the sections scanned * Scan the objects already referenced from the sections scanned
* above. More objects will be referenced and, if there are no memory * above.
* leaks, all the objects will be scanned. The list traversal is safe
* for both tail additions and removals from inside the loop. The
* kmemleak objects cannot be freed from outside the loop because their
* use_count was increased.
*/ */
repeat: scan_gray_list();
object = list_entry(gray_list.next, typeof(*object), gray_list);
while (&object->gray_list != &gray_list) {
cond_resched();
/* may add new objects to the list */
if (!scan_should_stop())
scan_object(object);
tmp = list_entry(object->gray_list.next, typeof(*object),
gray_list);
/* remove the object from the list and release it */
list_del(&object->gray_list);
put_object(object);
object = tmp;
}
if (scan_should_stop() || ++gray_list_pass >= GRAY_LIST_PASSES)
goto scan_end;
/* /*
* Check for new objects allocated during this scanning and add them * Check for new or unreferenced objects modified since the previous
* to the gray list. * scan and color them gray until the next scan.
*/ */
rcu_read_lock(); rcu_read_lock();
list_for_each_entry_rcu(object, &object_list, object_list) { list_for_each_entry_rcu(object, &object_list, object_list) {
spin_lock_irqsave(&object->lock, flags); spin_lock_irqsave(&object->lock, flags);
if ((object->flags & OBJECT_NEW) && !color_black(object) && if (color_white(object) && (object->flags & OBJECT_ALLOCATED)
get_object(object)) { && update_checksum(object) && get_object(object)) {
object->flags &= ~OBJECT_NEW; /* color it gray temporarily */
object->count = object->min_count;
list_add_tail(&object->gray_list, &gray_list); list_add_tail(&object->gray_list, &gray_list);
} }
spin_unlock_irqrestore(&object->lock, flags); spin_unlock_irqrestore(&object->lock, flags);
} }
rcu_read_unlock(); rcu_read_unlock();
if (!list_empty(&gray_list)) /*
goto repeat; * Re-scan the gray list for modified unreferenced objects.
*/
scan_end: scan_gray_list();
WARN_ON(!list_empty(&gray_list));
/* /*
* If scanning was stopped or new objects were being allocated at a * If scanning was stopped do not report any new unreferenced objects.
* higher rate than gray list scanning, do not report any new
* unreferenced objects.
*/ */
if (scan_should_stop() || gray_list_pass >= GRAY_LIST_PASSES) if (scan_should_stop())
return; return;
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment