kmemleak: fix kmemleak false positive report with HW tag-based kasan enable
authorKuan-Ying Lee <Kuan-Ying.Lee@mediatek.com>
Fri, 14 Jan 2022 22:04:04 +0000 (14:04 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 15 Jan 2022 14:30:25 +0000 (16:30 +0200)
With HW tag-based kasan enable, We will get the warning when we free
object whose address starts with 0xFF.

It is because kmemleak rbtree stores tagged object and this freeing
object's tag does not match with rbtree object.

In the example below, kmemleak rbtree stores the tagged object in the
kmalloc(), and kfree() gets the pointer with 0xFF tag.

Call sequence:
    ptr = kmalloc(size, GFP_KERNEL);
    page = virt_to_page(ptr);
    offset = offset_in_page(ptr);
    kfree(page_address(page) + offset);
    ptr = kmalloc(size, GFP_KERNEL);

A sequence like that may cause the warning as following:

 1) Freeing unknown object:

    In kfree(), we will get free unknown object warning in
    kmemleak_free(). Because object(0xFx) in kmemleak rbtree and
    pointer(0xFF) in kfree() have different tag.

 2) Overlap existing:

    When we allocate that object with the same hw-tag again, we will
    find the overlap in the kmemleak rbtree and kmemleak thread will be
    killed.

kmemleak: Freeing unknown object at 0xffff000003f88000
CPU: 5 PID: 177 Comm: cat Not tainted 5.16.0-rc1-dirty #21
Hardware name: linux,dummy-virt (DT)
Call trace:
 dump_backtrace+0x0/0x1ac
 show_stack+0x1c/0x30
 dump_stack_lvl+0x68/0x84
 dump_stack+0x1c/0x38
 kmemleak_free+0x6c/0x70
 slab_free_freelist_hook+0x104/0x200
 kmem_cache_free+0xa8/0x3d4
 test_version_show+0x270/0x3a0
 module_attr_show+0x28/0x40
 sysfs_kf_seq_show+0xb0/0x130
 kernfs_seq_show+0x30/0x40
 seq_read_iter+0x1bc/0x4b0
 seq_read_iter+0x1bc/0x4b0
 kernfs_fop_read_iter+0x144/0x1c0
 generic_file_splice_read+0xd0/0x184
 do_splice_to+0x90/0xe0
 splice_direct_to_actor+0xb8/0x250
 do_splice_direct+0x88/0xd4
 do_sendfile+0x2b0/0x344
 __arm64_sys_sendfile64+0x164/0x16c
 invoke_syscall+0x48/0x114
 el0_svc_common.constprop.0+0x44/0xec
 do_el0_svc+0x74/0x90
 el0_svc+0x20/0x80
 el0t_64_sync_handler+0x1a8/0x1b0
 el0t_64_sync+0x1ac/0x1b0
...
kmemleak: Cannot insert 0xf2ff000003f88000 into the object search tree (overlaps existing)
CPU: 5 PID: 178 Comm: cat Not tainted 5.16.0-rc1-dirty #21
Hardware name: linux,dummy-virt (DT)
Call trace:
 dump_backtrace+0x0/0x1ac
 show_stack+0x1c/0x30
 dump_stack_lvl+0x68/0x84
 dump_stack+0x1c/0x38
 create_object.isra.0+0x2d8/0x2fc
 kmemleak_alloc+0x34/0x40
 kmem_cache_alloc+0x23c/0x2f0
 test_version_show+0x1fc/0x3a0
 module_attr_show+0x28/0x40
 sysfs_kf_seq_show+0xb0/0x130
 kernfs_seq_show+0x30/0x40
 seq_read_iter+0x1bc/0x4b0
 kernfs_fop_read_iter+0x144/0x1c0
 generic_file_splice_read+0xd0/0x184
 do_splice_to+0x90/0xe0
 splice_direct_to_actor+0xb8/0x250
 do_splice_direct+0x88/0xd4
 do_sendfile+0x2b0/0x344
 __arm64_sys_sendfile64+0x164/0x16c
 invoke_syscall+0x48/0x114
 el0_svc_common.constprop.0+0x44/0xec
 do_el0_svc+0x74/0x90
 el0_svc+0x20/0x80
 el0t_64_sync_handler+0x1a8/0x1b0
 el0t_64_sync+0x1ac/0x1b0
kmemleak: Kernel memory leak detector disabled
kmemleak: Object 0xf2ff000003f88000 (size 128):
kmemleak:   comm "cat", pid 177, jiffies 4294921177
kmemleak:   min_count = 1
kmemleak:   count = 0
kmemleak:   flags = 0x1
kmemleak:   checksum = 0
kmemleak:   backtrace:
     kmem_cache_alloc+0x23c/0x2f0
     test_version_show+0x1fc/0x3a0
     module_attr_show+0x28/0x40
     sysfs_kf_seq_show+0xb0/0x130
     kernfs_seq_show+0x30/0x40
     seq_read_iter+0x1bc/0x4b0
     kernfs_fop_read_iter+0x144/0x1c0
     generic_file_splice_read+0xd0/0x184
     do_splice_to+0x90/0xe0
     splice_direct_to_actor+0xb8/0x250
     do_splice_direct+0x88/0xd4
     do_sendfile+0x2b0/0x344
     __arm64_sys_sendfile64+0x164/0x16c
     invoke_syscall+0x48/0x114
     el0_svc_common.constprop.0+0x44/0xec
     do_el0_svc+0x74/0x90
kmemleak: Automatic memory scanning thread ended

[akpm@linux-foundation.org: whitespace tweak]

Link: https://lkml.kernel.org/r/20211118054426.4123-1-Kuan-Ying.Lee@mediatek.com
Signed-off-by: Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Doug Berger <opendmb@gmail.com>
Cc: Mel Gorman <mgorman@techsingularity.net>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
mm/kmemleak.c

index b57383c17cf60706f45ae3d20b2295b181ed69a6..dc3758fdba68d412e8a5f4881acf23a75f4a2181 100644 (file)
@@ -381,15 +381,20 @@ static void dump_object_info(struct kmemleak_object *object)
 static struct kmemleak_object *lookup_object(unsigned long ptr, int alias)
 {
        struct rb_node *rb = object_tree_root.rb_node;
+       unsigned long untagged_ptr = (unsigned long)kasan_reset_tag((void *)ptr);
 
        while (rb) {
-               struct kmemleak_object *object =
-                       rb_entry(rb, struct kmemleak_object, rb_node);
-               if (ptr < object->pointer)
+               struct kmemleak_object *object;
+               unsigned long untagged_objp;
+
+               object = rb_entry(rb, struct kmemleak_object, rb_node);
+               untagged_objp = (unsigned long)kasan_reset_tag((void *)object->pointer);
+
+               if (untagged_ptr < untagged_objp)
                        rb = object->rb_node.rb_left;
-               else if (object->pointer + object->size <= ptr)
+               else if (untagged_objp + object->size <= untagged_ptr)
                        rb = object->rb_node.rb_right;
-               else if (object->pointer == ptr || alias)
+               else if (untagged_objp == untagged_ptr || alias)
                        return object;
                else {
                        kmemleak_warn("Found object by alias at 0x%08lx\n",
@@ -576,6 +581,7 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size,
        struct kmemleak_object *object, *parent;
        struct rb_node **link, *rb_parent;
        unsigned long untagged_ptr;
+       unsigned long untagged_objp;
 
        object = mem_pool_alloc(gfp);
        if (!object) {
@@ -629,9 +635,10 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size,
        while (*link) {
                rb_parent = *link;
                parent = rb_entry(rb_parent, struct kmemleak_object, rb_node);
-               if (ptr + size <= parent->pointer)
+               untagged_objp = (unsigned long)kasan_reset_tag((void *)parent->pointer);
+               if (untagged_ptr + size <= untagged_objp)
                        link = &parent->rb_node.rb_left;
-               else if (parent->pointer + parent->size <= ptr)
+               else if (untagged_objp + parent->size <= untagged_ptr)
                        link = &parent->rb_node.rb_right;
                else {
                        kmemleak_stop("Cannot insert 0x%lx into the object search tree (overlaps existing)\n",